说来惭愧,早已听过greenDao,但是一直没机会用,也就一直搁浅了这方面的知识,还好最近有这样的需求,就此记录一波(没想到拖了半年才进行记录,有点无语) ~

此blog主要讲述了greenDao基础使用,greenDao加密、greenDao升级,敬请学习 ~

Android中常见的本地数据存储方式有三种,根据使用场景,依次顺序如下所示:

  • sp存储(常见)
  • 文件存储 (常见)
  • 数据库存储 (相对较少)

本篇主讲数据库存储,采用的是 GreenDao 缓存数据到本地数据库 ~


  • 基础了解
  • 开发实战
  • 基础配置
  • 注解释义
  • 基础使用
  • 建表
  • 初始化 + 调用
  • 常用功能
  • 清除缓存
  • sql执行语句
  • 数据库升级
  • 升级知悉
  • 升级实现
  • 数据库加密
  • 小课堂
  • SQLite数据的存储空间有多大?
  • 为了数据安全,greenDao进行混淆
  • 如何实现本地数据库分页加载?
  • 常见问题
  • Unable to find method 'org.gradle.api.tasks.TaskInputs.property
  • java.io.File android.content.Context.getDatabasePath(java.lang.String)
  • Cannot update entity without key - was it inserted before?
  • java.lang.NoClassDefFoundError: org.greenrobot.greendao.database.StandardDatabase
  • android.database.sqlite.SQLiteException:table xxx has no column named XXX
  • android.database.sqlite.SQLiteException: near "$xxx": syntax error (code 1)
  • Expected unique result, but count was 2
  • Can't replace method in ...\mvp\model\xxx实体类.java:120 with generated versio


基础了解

在2016年初应该就已经有人开始用GreenDao了,很多人评价该数据库是一款高效的ORM(Object Relation Mapping)关系映射型数据库,而且我观察到该项目在近一年之内也一直有人员维护,从这方面可以看出该框架还是相对稳定的 ~

如果仅是框架稳定的话,并不足以满足开发者,关键还在于它自身的各种优势,主要优势有以下几点:

  1. 轻量级、且精简的数据库,库文件仅100k大小,编译时间低,可避免65k方法限制,内存开销最小化
  2. 性能最大化,存取速度快,每秒中可以操作数千个实体;支持缓存,能够将使用的过的实体存在缓存中,下次使用时可以直接从缓存中取,这样可以使性能提高N个数量级,比sqlite性能高30%
  3. 易于使用的 APIs,处于激活状态下的实体可以有更多操作方法
  4. 对 Android 进行高度优化,代码自动生成,greenDao 会根据modle类自动生成实体类(entities)和Dao对象,并且Dao对象是根据entities类量身定做的并且一 一对应
  5. 2.2以上版本,支持数据库加密,既支持android原生数据库SQLite,也支持SQLCipher(在SQLite基础上加密型数据库)
  6. greenDao3.0开始支持RxJava操作,证明框架开发者对于greenDao一直在进行功能扩展,发展力蛮不错

当集成greenDao后会自用生成DaoMaster 、DaoSession 、xxDao 三种类,分别承担不同的角色,具备不同的功能

1.DaoMaster - 数据库管理者

主要包含表的创建、删除,以及操作对应表的 Session ,当然还有一些封装好的OpenHelper,也就创建数据库的一个基本框架

greendao设置cursourwindow大小 greendao缓存_greenDao


DevOpenHelper、OpenHelper继承自DatabaseOpenHelper,主要是关于数据库的一些基本操作

greendao设置cursourwindow大小 greendao缓存_自动生成_02


2. DaoSession - 会话层

主要包含DaoSession构造方法,clear(),以及对应Dao的getDao方法

  • DaoSession构造方法:初始化创建、配置Dao类
  • clear():清楚Dao的配置信息
  • getDao:获取数据库内对应表(Dao)信息

greendao设置cursourwindow大小 greendao缓存_Android_03


3. Dao - 对应数据库的表操作类xxDao类一般代表着对应的表,内部封装一些该表的基本方法与表字段等操作方式

greendao设置cursourwindow大小 greendao缓存_数据库_04

编译后生成的xxDAO类,通常对应@Entity注解的java类;内部有更多的权限和方法来操作数据库表元素


开发实战
基础配置

bulid.gradle (Project)

dependencies {
   		//gradle版本和不需强关联
        classpath 'com.android.tools.build:gradle:3.2.0'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
        //Here:As 4.1 版本后我配置的是greendao 3.3.0 ,如下
        //classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0'
        
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

bulid.gradle(Module)

//注1:配置文件顶部加入greendao插件
apply plugin: 'org.greenrobot.greendao'

android {
 	//注2:android标签下加入以下配置,主要设置插件生成类的存储位置和版本记录
    greendao {
        //数据库schema版本(数据库版本号)
        schemaVersion 1
        //设置DaoMaster、DaoSession、Dao目录,生成源文件的路径(默认源文件目录是在build目录-build/generated/source/greendao)
        targetGenDir 'src/main/java'
        //设置DaoMaster、DaoSession、Dao包名,也就是要放置这些类的包的全路径(一般为项目包名+具体包名)
        daoPackage 'com.nk.machine.greendao'
        //是否需要自动生成单元测试 true:自动生成 false:不生成(默认false)
        generateTests false
    }
}

dependencies {
    //注3:加入greendao library,这样才可能调用greendao的方法
    implementation 'org.greenrobot:greendao:3.2.2'
    implementation 'org.greenrobot:greendao-generator:3.2.2'
    //Here:As 4.1 版本后我配置的是greendao 3.3.0 ,如下
    //implementation 'org.greenrobot:greendao:3.3.0'
    //implementation 'org.greenrobot:greendao-generator:3.3.0'
}

Here:配置完成后记得编译一下项目,然后会生成对应的greenddao配置类

greendao设置cursourwindow大小 greendao缓存_Android_05


注解释义

greenDao 也算是一款数据库注解框架,所以首先应该先了解其基本的注解含义 ~

此处注解大多会在Model中用到,相当于建表的字段的一些限制

注解

含义

@Entity

被注解的实体类,一般都会在数据库中生成对应的表!注意:只有被@Entity注释的实体类才能被dao类操作

@Id (基础注解)

对象的Id,使用Long类型(否则会报错)作为EntityId(对象的Id),通常设置为@Id(autoincrement = true) 表示主键自增,如果false就会使用旧值

@Unique(索引注解)

属性、字段唯一性,一般与@Id合用(该属性值必须在数据库中是唯一值,有且仅有一处)

@Property(基础注解)

可以自定义数据库字段名,如未设置则默认使用原始字段名,注意外键不能使用该属性。如(@Property(nameInDb = “age”,则表示当前属性传入数据库后存储在age字段内)

@NotNull (基础注解)

设置数据库表当前列不能为空(该属性不可为空)

@Transient(基础注解)

使用该注解的属性不会被存入数据库的列字段中

@Generated

编译后自动生成的构造函数、方法等的注释,提示构造函数、方法等不能被修改

@ToOne、@ToMany(关系注解)

用来声明”对一”和“对多”关系,举例说明:学生与学校之间一对多的关系(一个学生对应一个学校,一个学校对应有多个学生)

@OrderBy

排序

Entity 属性释义

注解

属性

含义

Entity

schema

greenDAO当前实体属于哪个 schema(数据库)

schema active

标记一个实体处于活跃状态,活动实体有更新、删除和刷新方法

nameInDb

在数据库中使用的表别名,默认使用当前类名

indexes

定义索引,可以跨越多个列

createInDb

是否创建数据库表(默认:true)

generateConstructors

是否自动生成全参构造方法(同时会生成一个无参构造方法)(默认:true)

generateGettersSetters

是否自动生成 getter/setter 方法(默认:true)

greenDao - sql常见api

api

意义

list()

所有实体都将被加载到内存中

listLazy()

实体懒加载到内存,必须close;(推荐使用)

listLazyUncached()

延迟加载不缓存数据, 每次访问结果集的时候都是从数据库中加载,而不使用缓存,必须close;

listIterator()

自己遍历数据, 可以通过迭代遍历按需加载的数据结果集(lazily)。数据没有缓存;一旦所有元素被访问或者遍历完成,来自于listLazy()的cached lazy和来自listIterator()的lazy iterator会自动关闭cursor必须手动close;

orderAsc

升序排序

orderDesc

降序排序

eq()

==

noteq()

!=

gt()

>

lt()

<

ge

>=

le

<=

like()

包含

between

俩者之间

in

在某个值内

notIn

不在某个值内

where

当,条件筛查

or

抑或,条件筛查

and

且,条件筛查

join

多表筛查

detachAll

清除指定Dao类的缓存

daoSession.clear()

清除所所有的缓存

isNull()

为空

isNotNull()

不为空

insert

插入数据

delete

按对象删除

deleteByKey

按主键删除

deleteInTx

删除多条记录

deleteAll()

全部删除

update

修改一条记录

updateInTx

修改多条记录

queryRaw

查询一条记录

QueryBuilder

查询多条记录


基础使用

关于基础使用主要包含greenDao的一个建表+初始化库+调用的基本功能,具体如下 ~

建表

之前的基础配置,相当于一个建库的过程,建库之后我们常规的是建表,而Android使用greenDao建表的方式就是创建对应的Model+@Entity

因为我是为了做错误日志使用的greenDao,所以这里我以ErrorBean为例,要注意以下几点

  • @Id(autoincrement = true)、 @Unique 必用注解,id必须为Long类型
  • @Nullable、@Property 常用注解,若未使用@Property 则默认字段为表字段,如使用@Property,则需通过nameInDb重新定义表字段
  • 无需手动生成构造方法与set、get等方法,字段定义完成后build(编译)项目即可自动生成
package com.xx.xxx.model;

import android.support.annotation.Nullable;

import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.Unique;
import org.greenrobot.greendao.annotation.Generated;

/**
 * @author MrLiu
 * @date 2021/1/18
 * desc 本地记录-收集错误原因
 */
@Entity
public class ErrorBean {

    /**
     * 主键自增 - 唯一
     */
    @Id(autoincrement = true)
    @Unique
    private Long id;

    /**
     * 错误时间
     */
    @Nullable
    @Property(nameInDb = "occurrenceTime")
    private long occurrenceTime;

    /**
     * 设备Id
     */
    @Nullable
    @Property(nameInDb = "machineId")
    private String machineId;

    /**
     * 错误类型
     */
    @Nullable
    @Property(nameInDb = "type")
    private int type;

    /**
     * 异常信息
     */
    @Nullable
    @Property(nameInDb = "errorInfo")
    private String errorInfo;

    /**
     * 错误原因
     */
    @Nullable
    @Property(nameInDb = "errorMsg")
    private String errorMsg;

    /**
     * 本地数据是否已上传后台
     */
    @Nullable
    @Property(nameInDb = "uploadState")
    private boolean uploadState;

biild(编译)项目后,自动生成的ErrorBean

package com.xx.xxx.model;

import android.support.annotation.Nullable;

import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.Unique;
import org.greenrobot.greendao.annotation.Generated;

/**
 * @author MrLiu
 * @date 2021/1/18
 * desc 本地记录-收集错误原因
 */
@Entity
public class ErrorBean {

    /**
     * 主键自增 - 唯一
     */
    @Id(autoincrement = true)
    @Unique
    private Long id;

    /**
     * 错误时间
     */
    @Nullable
    @Property(nameInDb = "occurrenceTime")
    private long occurrenceTime;

    /**
     * 设备Id
     */
    @Nullable
    @Property(nameInDb = "machineId")
    private String machineId;

    /**
     * 错误类型
     * 注:如type为0,可作为扩展原因
     */
    @Nullable
    @Property(nameInDb = "type")
    private int type;

    /**
     * 异常信息
     */
    @Nullable
    @Property(nameInDb = "errorInfo")
    private String errorInfo;

    /**
     * 错误原因
     */
    @Nullable
    @Property(nameInDb = "errorMsg")
    private String errorMsg;

    /**
     * 本地数据是否已上传后台
     */
    @Nullable
    @Property(nameInDb = "uploadState")
    private boolean uploadState;

    public ErrorBean() {
        super();
    }

    @Generated(hash = 1045555638)
    public ErrorBean(Long id, long occurrenceTime, String machineId, int type,
                     String errorInfo, String errorMsg, boolean uploadState) {
        this.id = id;
        this.occurrenceTime = occurrenceTime;
        this.machineId = machineId;
        this.type = type;
        this.errorInfo = errorInfo;
        this.errorMsg = errorMsg;
        this.uploadState = uploadState;
    }

    public String getMachineId() {
        return machineId;
    }

    public void setMachineId(String machineId) {
        this.machineId = machineId;
    }

    public long getOccurrenceTime() {
        return occurrenceTime;
    }

    public void setOccurrenceTime(long occurrenceTime) {
        this.occurrenceTime = occurrenceTime;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getErrorInfo() {
        return errorInfo;
    }

    public void setErrorInfo(String errorInfo) {
        this.errorInfo = errorInfo;
    }

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Nullable
    public boolean isUploadState() {
        return uploadState;
    }

    public void setUploadState(@Nullable boolean uploadState) {
        this.uploadState = uploadState;
    }


    public boolean getUploadState() {
        return this.uploadState;
    }
}

build成功后,一般对应表都已建好,可以在之前配置的greenDao目录下查看对应表信息

greendao设置cursourwindow大小 greendao缓存_数据库_06

初始化 + 调用

一个关于greenDao的基础管理类GreenBasic

package com.xx.xxx.base;

import android.content.Context;

import com.nk.machine.greendao.DaoMaster;
import com.nk.machine.greendao.DaoSession;

import org.greenrobot.greendao.database.Database;


/**
 * @author MrLiu
 * @date 2021/1/20
 * desc greenDao管理类
 */
public class GreenBasic {
    private static String DB_NAME = "nkDao.db";

    public static GreenBasic basic;
    public static DaoMaster.DevOpenHelper helper;
    private static DaoSession daoSession;

    public static GreenBasic getInstance() {
        if (basic == null) {
            synchronized (GreenBasic.class) {
                if (basic == null) {
                    basic = new GreenBasic();
                }
            }
        }
        return basic;
    }

    public void initGreenDao(Context context) {
        //创建数据库
        helper = new DaoMaster.DevOpenHelper(context, DB_NAME, null);
        //获取可写数据库
        Database db = helper.getWritableDb();
        //获取数据库对象
        DaoMaster daoMaster = new DaoMaster(db);
        //获取Dao对象管理者
        daoSession = daoMaster.newSession();
    }

    public DaoSession getSession() {
        return daoSession;
    }

    public void closeDao() {
        if (daoSession != null) {
            daoSession.clear();
            daoSession = null;
        }
        if (helper != null) {
            helper.close();
            helper = null;
        }
    }
}

在Application中初始化greenDao数据库(记得把application配置到AndroidMainfest中 ~)

package com.xx.xxx.base;

import android.app.Application;
import android.content.Context;


public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化本地数据库
        GreenBasic.getInstance().initGreenDao(this);
    }
  }

调用方式,这里以查询为例

//通过数据库获取到对应的表信息
 ErrorBeanDao errorBeanDao = GreenBasic.getInstance().getSession().getErrorBeanDao();
 //表内queryBuilder查询,通过where定义筛查条件,可以单、多条件,返回体可以是list,也可以是单个实体类
 List<ErrorBean> errorList = errorBeanDao.queryBuilder().where(ErrorBeanDao.Properties.UploadState.eq(false)).list();

常用功能

主要方法就是数据库增删改查四件套了,具体区别就是细节讲解,我都做了一些记录

这里的示例表,是一个我在项目中使用场景较多的商品表 ProductEntity

package com.xx.xxx.dao.model;


import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.NotNull;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.Unique;
import org.greenrobot.greendao.annotation.Generated;


/**
 * @author MrLiu
 * @date 2021/3/1
 * desc Dao_商品部分信息存储
 */
@Entity
public class ProductEntity {

    @Id
    @Unique
    @NotNull
    private Long id;

    /**
     * 商品分类id
     */
    @NotNull
    @Property(nameInDb = "categoryId")
    private String categoryId;

    /**
     * 商品id
     */
    @NotNull
    @Property(nameInDb = "productId")
    private String productId;

    /**
     * 商品名称
     */
    @NotNull
    @Property(nameInDb = "name")
    private String name;

    /**
     * 商品图片URL
     */
    @NotNull
    @Property(nameInDb = "imageUrl")
    private String imageUrl;

    /**
     * 存储位置
     */
    @NotNull
    @Property(nameInDb = "localAddress")
    String localAddress;

    /**
     * 库存数量
     */
    @Property(nameInDb = "stockNum")
    private int stockNum;

    /**
     * 商品单位
     */
    @Property(nameInDb = "unit")
    private String unit;
}

baby,look here :在正式开发中涉及到增删改查的时候,要记得根据场景先查数据是否存在,假设你删除或修改一个不存在数据时,可是会报错的哦~

  • 单条新增(.insert(model)
GreenBasic.getInstance().getSession().getProductEntityDao().insert(model);

示例(如何新增多条数据?老铁我反正是for循环的 ~)

//仅是示例,对应的值正常是从参数内传入的 ~
    @Override
    public void insert() {
        ProductEntity productEntity = new ProductEntity();
        productEntity.setCategoryId(categoryId);
        productEntity.setProductId(productId);
        productEntity.setImageUrl(imageUrl);
        productEntity.setLocalAddress(localAddress);
        productEntity.setName(name);
        productEntity.setStockNum(stockNum);
        productEntity.setUnit(unit);
        //将已设置好的model直接insert即可
        GreenBasic.getInstance().getSession().insert(productEntity);
    }
  • 批量新增(未尝试 .insertInTx(list)

批量新增方式一般有俩种,一种是框架自带的insertInTx方法,一种就我用的for循环方法,本质没啥差别 - -

GreenBasic.getInstance().getSession().getProductEntityDao().insertInTx(批量数据的list);

示例

public static void insertData(Context context, List<ProductEntity> list) {
        if (null == list || list.size() <= 0) {
            return;
        }
        GreenBasic.getInstance().getSession().getProductEntityDao().insertInTx(list);
    }
  • 单条删除(根据model删除 .delete(model)
GreenBasic.getInstance().getSession().getProductEntityDao().delete(new ProductEntity (参数值自己传,但是一般使用的model,都是从表内筛查出的数据,具体如下));

示例(正式场景中的删除,大多都是条件筛查后的某些数据)

@Override
    public void delete() {
        ProductEntityDao productEntityDao = GreenBasic.getInstance().getSession().getProductEntityDao();
        ProductEntity unique = productEntityDao.queryBuilder().where(ProductEntityDao.Properties.ProductId.eq(productId)).unique();
        productEntityDao.delete(unique);
    }
  • 单条删除(根据id删除 .deleteByKey(id)
//删除一条数据,根据Key 也就是id
 GreenBasic.getInstance().getSession().getProductEntityDao().deleteByKey(key也就是id);

示例

/**
     * 根据id删除数据至数据库
     * @param id      删除具体内容
     */
    public static void deleteByKeyData(long id) {
       GreenBasic.getInstance().getSession().getProductEntityDao().delete.deleteByKey(id);
 	}
  • 批量删除(未尝试 .deleteInTx(list)、.deleteByKeyInTx(id)
//删除一组数据(对象)
 GreenBasic.getInstance().getSession().getProductEntityDao().deleteInTx(List<ProductEntity>list);
 //删除一组数据(根据id)
 GreenBasic.getInstance().getSession().getProductEntityDao().deleteByKeyInTx(List<Long>idList);
  • 全部删除(慎用 .deleteAll()
//删除所有数据
 GreenBasic.getInstance().getSession().getProductEntityDao().deleteAll();

其实在开发中你需要修改某条数据时,需要优先查询到该条数据,然后set对应值后,重新update到设置后的类即可 ~

单条修改 .update(model) ,多条修改for循环去吧 ~

GreenBasic.getInstance().getSession().getProductEntityDao().update(model);

here :尾部 .unique 返回的为单个实体类,.list返回的为对应实体类的listhere:当数据不止一条时,如果使用.unique会报错,为保险可以常规使用list,但是要做判断处理

  • 单一查询(条件筛查 queryBuilder().where(UserDao.Properties.Name.eq("")).list()

关于load(Long key)主键查询,queryBuilder().list() list返回,queryRaw(String where,String selectionArg)自行根据场景使用,我感觉我写的应该够用了~

ProductEntityDao productEntityDao= GreenBasic.getInstance().getSession().getProductEntityDao();
 //此处采用.unique返回为单个实体类,如使用list则返回为对应list
 ProductEntity info = ProductEntityDao.queryBuilder().where(ProductEntityDao .Properties.Id.eq(55)).unique();
  • 全部查询(.loadAll()
List<ProductEntity> productList = GreenBasic.getInstance().getSession().getProductEntityDao().loadAll();
清除缓存
  • 清除所有的缓存
daoSession.clear();
  • 清除指定Dao类的缓存
TestDamo testDao = daoSession.getTestDao();
 testDao.detachAll();
sql执行语句

在greenDao初始化时加入以下配置

//初始化sql执行日志
  QueryBuilder.LOG_SQL = true;
  QueryBuilder.LOG_VALUES = true;

示例

public void initGreenDao(Context context) {
        //初始化sql执行日志
        QueryBuilder.LOG_SQL = true;
        QueryBuilder.LOG_VALUES = true;
        //创建数据库
        helper = new DaoMaster.DevOpenHelper(context, DB_NAME, null);
        //获取可写数据库
        Database db = getWritableDatabase("password");
        //获取数据库对象
        DaoMaster daoMaster = new DaoMaster(db);
        //获取Dao对象管理者
        daoSession = daoMaster.newSession();
    }

数据库升级

关于greenDao数据库升级是一个很常见的功能,当原表字段改变,或增删一些表时,就需要去更新本地数据库,这里做一下记录,具体结果我并未亲自尝试

网上看了很多升级方法都类似,但是挺多不全的,这里仅记录我总结的版本升级方式

升级知悉

升级原理

  1. 创建临时表
  2. 将原始表内数据存储到临时表内
  3. 删除原始表
  4. 将临时表数据存储到升级后的表内
  5. 删除临时表

升级实现过程

  1. 新建OpenHelper类 继承 DaoMaster.OpenHelper,然后重写onUpgrade方法
  2. 修改升级目标库的版本号
  3. 修改初始化greenDao库时Helper类
升级实现
  1. 新建OpenHelper(升级类)继承 DaoMaster.OpenHelper,重写 onUpgrade 方法 ~
package nkwl.com.greendaodemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;


import org.greenrobot.greendao.database.Database;

import nkwl.com.greendaodemo.greendao.DaoMaster;
import nkwl.com.greendaodemo.greendao.ProductDao;

/**
 1. @author MrLiu
 2. @date 2021/6/18
 3. desc
 */
public class NKOpenHelper extends DaoMaster.OpenHelper {
    public NKOpenHelper(Context context, String name) {
        super(context, name);
    }

    public NKOpenHelper(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.getInstance().migrate(db, ProductDao.class);
    }
}

以上方法是否很熟悉,然后你找不到MigrationHelper类(会放置于该段底部)?这里帮你找到了对应方式,以下是帮你抽离出来得migrate方法~

public void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        generateTempTables(db, daoClasses);
        DaoMaster.dropAllTables(db, true);
        DaoMaster.createAllTables(db, false);
        restoreData(db, daoClasses);
    }

NKOpenHelper openHelper = new NKOpenHelper(this);
        DaoMaster daoMaster = new DaoMaster(openHelper .getWritableDb());
        DaoSession daoSession = daoMaster.newSession();
        //需要更新的数据库表
        DemoDao demoDemo= daoSession.getDemoDao();
  1. build 中更改 greendao的schemaVersion版本
  2. greendao设置cursourwindow大小 greendao缓存_数据库_07

  3. 改变初始化数据库中创建数据库的语句为我们升级数据库的语句,具体如下
public void initGreenDao(Context context) {
        //创建数据库
       //DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, DB_NAME, null);
       	//升级数据库
       	NKOpenHelper helper=new NKOpenHelper (this, DB_NAME, null);
        //获取可写数据库
        Database db = helper.getWritableDb();
        //获取数据库对象
        DaoMaster daoMaster = new DaoMaster(db);
        //获取Dao对象管理者
        daoSession = daoMaster.newSession();
    }

补充:MigrationHelper

package nkwl.com.greendaodemo;

/**
 * @author MrLiu
 * @date 2021/1/22
 * desc
 */
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;

import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.internal.DaoConfig;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import nkwl.com.greendaodemo.greendao.DaoMaster;

/**
 * 数据库升级
 */

public class MigrationHelper {
    private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS";
    private static MigrationHelper instance;

    public static MigrationHelper getInstance() {
        if (instance == null) {
            instance = new MigrationHelper();
        }
        return instance;
    }

    public void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        generateTempTables(db, daoClasses);
        DaoMaster.dropAllTables(db, true);
        DaoMaster.createAllTables(db, false);
        restoreData(db, daoClasses);
    }

    private void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        for (int i = 0; i < daoClasses.length; i++) {
            DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
            String divider = "";
            String tableName = daoConfig.tablename;
            String tempTableName = daoConfig.tablename.concat("_TEMP");
            ArrayList<String> properties = new ArrayList<String>();
            StringBuilder createTableStringBuilder = new StringBuilder();
            createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" (");
            for (int j = 0; j < daoConfig.properties.length; j++) {
                String columnName = daoConfig.properties[j].columnName;

                if (getColumns(db, tableName).contains(columnName)) {
                    properties.add(columnName);

                    String type = null;

                    try {
                        type = getTypeByClass(daoConfig.properties[j].type);
                    } catch (Exception exception) {
                    }
                    createTableStringBuilder.append(divider).append(columnName).append(" ").append(type);

                    if (daoConfig.properties[j].primaryKey) {
                        createTableStringBuilder.append(" PRIMARY KEY");
                    }
                    divider = ",";
                }
            }
            createTableStringBuilder.append(");");
            db.execSQL(createTableStringBuilder.toString());

            StringBuilder insertTableStringBuilder = new StringBuilder();
            insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" (");
            insertTableStringBuilder.append(TextUtils.join(",", properties));
            insertTableStringBuilder.append(") SELECT ");
            insertTableStringBuilder.append(TextUtils.join(",", properties));
            insertTableStringBuilder.append(" FROM ").append(tableName).append(";");

            db.execSQL(insertTableStringBuilder.toString());
        }
    }

    private void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        for (int i = 0; i < daoClasses.length; i++) {
            DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
            String tableName = daoConfig.tablename;
            String tempTableName = daoConfig.tablename.concat("_TEMP");
            ArrayList<String> properties = new ArrayList();
            for (int j = 0; j < daoConfig.properties.length; j++) {
                String columnName = daoConfig.properties[j].columnName;

                if (getColumns(db, tempTableName).contains(columnName)) {
                    properties.add(columnName);
                }
            }
            StringBuilder insertTableStringBuilder = new StringBuilder();

            insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
            insertTableStringBuilder.append(TextUtils.join(",", properties));
            insertTableStringBuilder.append(") SELECT ");
            insertTableStringBuilder.append(TextUtils.join(",", properties));
            insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");

            StringBuilder dropTableStringBuilder = new StringBuilder();

            dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);

            db.execSQL(insertTableStringBuilder.toString());
            db.execSQL(dropTableStringBuilder.toString());
        }
    }

    private String getTypeByClass(Class<?> type) throws Exception {
        if (type.equals(String.class)) {
            return "TEXT";
        }
        if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) {
            return "INTEGER";
        }
        if (type.equals(Boolean.class)) {
            return "BOOLEAN";
        }
        Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString()));
        throw exception;
    }

    private static List<String> getColumns(Database db, String tableName) {
        List<String> columns = new ArrayList<String>();
        Cursor cursor = null;
        try {
            cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null);
            if (cursor != null) {
                columns = new ArrayList<String>(Arrays.asList(cursor.getColumnNames()));
            }
        } catch (Exception e) {
            Log.v(tableName, e.getMessage(), e);
            e.printStackTrace();
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return columns;
    }
}

数据库加密

greenDao提供了数据库加密的功能,具体是否使用取决于自己 > < ~

通过部分blog发现需要在build加入下方依赖(可以先不加,进行后方尝试,如有问题回头在加上)

implementation 'org.greenrobot:greendao:3.2.2'
  implementation 'net.zetetic:android-database-sqlcipher:3.5.1'

加密数据库,主要使用到了以下的俩个方法

greendao设置cursourwindow大小 greendao缓存_Android_08


红标标记为数据库未加密与加密的区别

greendao设置cursourwindow大小 greendao缓存_greenDao_09


加密方法

这里的加密状态直接声明写死,封装方法取决于自己,主要注意的就是getEncryptedWritableDb、getEncryptedReadableDb加密方法,getWritableDb、getReadableDb常规方法

//加密状态
    private static boolean encryptState = true;
    
    /**
     * 获取可写数据库
     *
     * @param password
     * @return
     */
    public static Database getWritableDatabase(String password) {
        if (null == helper) {
            getInstance();
        }
        //加密状态
        if (encryptState) {
            return helper.getEncryptedWritableDb(password);
        } else {
            return helper.getWritableDb();
        }
    }

    /**
     * 获取可读数据库
     *
     * @return
     */
    public static Database getReadableDatabase(String password) {
        if (null == helper) {
            getInstance();
        }
        //加密状态
        if (encryptState) {
            return helper.getEncryptedReadableDb(password);
        } else {
            return helper.getReadableDb();
        }
    }

自我封装

package nkwl.com.greendaodemo;

import android.content.Context;
import android.se.omapi.Session;

import org.greenrobot.greendao.database.Database;

import nkwl.com.greendaodemo.greendao.DaoMaster;
import nkwl.com.greendaodemo.greendao.DaoSession;
import nkwl.com.greendaodemo.greendao.ProductDao;

/**
 * @author MrLiu
 * @date 2020/7/23
 * desc greenDao基础配置
 */
public class GreenBasic {
    //数据库名称
    private static String DB_NAME = "firstDao.db";
    //加密状态
    private static boolean encryptState = true;

    public static GreenBasic basic;
    public static DaoMaster.DevOpenHelper helper;
    private static DaoSession daoSession;

    public static GreenBasic getInstance() {
        if (basic == null) {
            synchronized (GreenBasic.class) {
                if (basic == null) {
                    basic = new GreenBasic();
                }
            }
        }
        return basic;
    }

    public void initGreenDao(Context context) {
        //创建数据库
        helper = new DaoMaster.DevOpenHelper(context, DB_NAME, null);
        //获取可写数据库
        Database db = getWritableDatabase("password");
        //获取数据库对象
        DaoMaster daoMaster = new DaoMaster(db);
        //获取Dao对象管理者
        daoSession = daoMaster.newSession();
    }


    /**
     * 获取可写数据库
     *
     * @param password
     * @return
     */
    public static Database getWritableDatabase(String password) {
        if (null == helper) {
            getInstance();
        }
        //加密状态
        if (encryptState) {
            return helper.getEncryptedWritableDb(password);
        } else {
            return helper.getWritableDb();
        }
    }

    /**
     * 获取可读数据库
     *
     * @return
     */
    public static Database getReadableDatabase(String password) {
        if (null == helper) {
            getInstance();
        }
        //加密状态
        if (encryptState) {
            return helper.getEncryptedReadableDb(password);
        } else {
            return helper.getReadableDb();
        }
    }

    public DaoSession getSession() {
        return daoSession;
    }

    public void closeDao() {
        if (daoSession != null) {
            daoSession.clear();
            daoSession = null;
        }
        if (helper != null) {
            helper.close();
            helper = null;
        }
    }
}

小课堂

仅记录一些我自己有思考的问题 ~

SQLite数据的存储空间有多大?

Sp的存储空间一般根据手机内存大小,虚拟机自行分配,但是一般都不会很大,但是SQLite db一般都存储在SD卡上,所以完全跟着手机内存走,故此有足够的空间够我们使用 > <

为了数据安全,greenDao进行混淆
#greendao start#
    -keep class org.greenrobot.greendao.**{*;}
    -keep public interface org.greenrobot.greendao.**
    -keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
     public static java.lang.String TABLENAME;
     }
    -keep class **$Properties
    -keep class net.sqlcipher.database.**{*;}
    -keep public interface net.sqlcipher.database.**
    -dontwarn net.sqlcipher.database.**
    -dontwarn org.greenrobot.greendao.**
    #greendao end#
如何实现本地数据库分页加载?

从别的blog找的伪代码,未亲自尝试,仅做记录

/**
     * 查询所有数据
     *  分页加载
     * @param context
     * @param pageSize 当前第几页
     * @param pageNum  每页显示多少个
     * @return
     */
    public static List<BaseRecordStation> queryAll(Context context, int pageSize, int pageNum) {
        QueryBuilder<BaseRecordStation> builder = DbManager.getDaoSession(context, DB_NAME).getBaseRecordStationDao().queryBuilder();
        List<BaseRecordStation> list = builder
                .offset(pageSize - 1)//从0开始的
                .limit(pageNum)
                .orderDesc(BaseRecordStationDao.Properties.Id)
                .build()
                .list();
        return list;
    }

常见问题
Unable to find method 'org.gradle.api.tasks.TaskInputs.property

GreenDao兼容问题-提示:Unable to find method 'org.gradle.api.tasks.TaskInputs.property

解决方式:降级或升级GreenDao依赖版本,如使用As开发工具升级版本可根据报黄警告,直接完成替换操作,如需降低版本需 前往此处查看GreenDao历史版本 ~

java.io.File android.content.Context.getDatabasePath(java.lang.String)

调用getWritableDb-提示:java.io.File android.content.Context.getDatabasePath(java.lang.String)

解决方式:new SQLiteOpenHelper()构造参数传入的Context不可为Application上下文,否则会报出 java.io.File android.content.Context.getDatabasePath(java.lang.String)’ on a null object reference 异常

Cannot update entity without key - was it inserted before?

greendao update-提示:Cannot update entity without key - was it inserted before?

原因:在调用update()时传入的主键为 null
应用环境:表中满足某条件的记录不重复,有则改之,无则’加冕’
解决方法:查询表中满足条件的记录,取其id赋值给新记录
问题排查,最终发现传递过来的数据ID==主键为Null,于是我自己设置了一个ID,就可以了

java.lang.NoClassDefFoundError: org.greenrobot.greendao.database.StandardDatabase

Android版本兼容-提示:java.lang.NoClassDefFoundError: org.greenrobot.greendao.database.StandardDatabase

场景:Andorid5.0以上可正常使用GreenDao框架,Android4.4使用的话就会报错,这个问题借鉴于此

解决:首先在android5.0以上不牵扯MultiDex分包问题,但是在android4.4甚至以下版本就有这个,需引入下方依赖

compile 'com.android.support:multidex:1.0.0'

引入这个来解决分包问题

  • 一. 从sdk\extras\android\support\multidex\library\libs 目录将android-support-multidex.jar导入工程中
  • 二. 如果你的工程中已经含有Application类,那么让它继承android.support.multidex.MultiDexApplication类,
    如果你的Application已经继承了其他类并且不想做改动,那么还有另外一种使用方式,覆写attachBaseContext()方法:
public class MyApplication extends FooApplication {  
    @Override  
    protected void attachBaseContext(Context base) {  
        super.attachBaseContext(base);  
        MultiDex.install(this);  
    }  
}

然后这样的话就可以解决我们的项目在android4.4以下版本中报错找不到

android.database.sqlite.SQLiteException:table xxx has no column named XXX

android.database.sqlite.SQLiteException:table xxx has no column named XXX

1:一般都是数据库增加了数据字段,而没有升级数据库的版本version
2:先卸载原来的应用,重新安装
3:数据库直接执行了onUpgrade方法,而你的创建表方法写在了onCreate()方法,(我的就是这种错误,前两种都检查了…)----这个时候要在onUpgrade方法中先执行db.delete(TAB_NAME,null,null),然后执行创建操作db.execSQL(…)

android.database.sqlite.SQLiteException: near “$xxx”: syntax error (code 1)

报错:android.database.sqlite.SQLiteException: near "$xxx": syntax error (code 1)

根据错误示意在使用SQLite动态创建表时报错,多了一个$xxx字段

  • 首先查看该类或表中是否存在这个字段?如不存在请继续往下看 ~
  • 因表字段是通过注解方式实现的,As4.1 可通过Setting - Build,Execution… - Debugger - Data Views - HotSwap 进行勾选/取消勾选操作 ~
Expected unique result, but count was 2

报错:Expected unique result, but count was 2

使用greendao获取数据,如下query.unque。如果当前的记录数大于一条,再使用query.unique则会报如题的报错。

Query<EntityBuildingPicInfo> query=entityBuildingPicInfoDao.queryBuilder().where(EntityBuildingPicInfoDao.Properties.Buildinginfo_uuid.eq(longID)).build();
   EntityBuildingPicInfo entityBuildingPicInfo=query.unique();
Can’t replace method in …\mvp\model\xxx实体类.java:120 with generated versio

报错:Can't replace method in ...\mvp\model\xxx实体类.java:120 with generated version

查看对应报错model内的方法是否是通过As 或 Gsonformat工具生成,如果是的话进行删除,因为greenDao其内部自带注解方式,当注解好相关model后,通过编译项目即可自动生成满足greenDao使用的model ~