Andorid进阶之路 - greenDao数据库存储

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

数据库相关

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

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

https://blog.csdn.net/qq_20451879/article/details/121922292

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

基础了解

在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,也就创建数据库的一个基本框架
在这里插入图片描述
DevOpenHelper、OpenHelper继承自DatabaseOpenHelper,主要是关于数据库的一些基本操作
在这里插入图片描述
2. DaoSession - 会话层

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

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

在这里插入图片描述
3. Dao - 对应数据库的表操作类

xxDao类一般代表着对应的表,内部封装一些该表的基本方法与表字段等操作方式
在这里插入图片描述

编译后生成的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 也算是一款数据库注解框架,所以首先应该先了解其基本的注解含义 ~

此处注解大多会在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 属性释义

注解属性含义
EntityschemagreenDAO当前实体属于哪个 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的基础管理类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返回的为对应实体类的list
here:当数据不止一条时,如果使用.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. 改变初始化数据库中创建数据库的语句为我们升级数据库的语句,具体如下

    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'

加密数据库,主要使用到了以下的俩个方法
在这里插入图片描述
红标标记为数据库未加密与加密的区别
在这里插入图片描述
加密方法

这里的加密状态直接声明写死,封装方法取决于自己,主要注意的就是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 ~

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值