说来惭愧,早已听过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)关系映射型数据库
,而且我观察到该项目在近一年之内也一直有人员维护,从这方面可以看出该框架还是相对稳定的 ~
如果仅是框架稳定的话,并不足以满足开发者,关键还在于它自身的各种优势,主要优势有以下几点:
-
轻量级、且精简的数据库
,库文件仅100k大小,编译时间低,可避免65k方法限制,内存开销最小化 -
性能最大化,存取速度快
,每秒中可以操作数千个实体;支持缓存,能够将使用的过的实体存在缓存中,下次使用时可以直接从缓存中取,这样可以使性能提高N个数量级,比sqlite性能高30% -
易于使用的 APIs
,处于激活状态下的实体可以有更多操作方法 -
对 Android 进行高度优化
,代码自动生成,greenDao 会根据modle类自动生成实体类(entities)和Dao对象,并且Dao对象是根据entities类量身定做的并且一 一对应 -
2.2以上版本,支持数据库加密
,既支持android原生数据库SQLite,也支持SQLCipher(在SQLite基础上加密型数据库) - 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 属性释义
注解 | 属性 | 含义 |
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的基础管理类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数据库升级是一个很常见的功能,当原表字段改变,或增删一些表时,就需要去更新本地数据库
,这里做一下记录,具体结果我并未亲自尝试
网上看了很多升级方法都类似,但是挺多不全的,这里仅记录我总结的版本升级方式
升级知悉
升级原理
- 创建临时表
- 将原始表内数据存储到临时表内
- 删除原始表
- 将临时表数据存储到升级后的表内
- 删除临时表
升级实现过程
- 新建OpenHelper类 继承 DaoMaster.OpenHelper,然后重写onUpgrade方法
- 修改升级目标库的版本号
- 修改初始化greenDao库时Helper类
升级实现
- 新建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();
- build 中更改 greendao的schemaVersion版本
- 改变初始化数据库中创建数据库的语句为我们升级数据库的语句,具体如下
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 ~