greenDAO 官网:http://greenrobot.org/greendao/
greenDAO 的 Github 地址:https://github.com/greenrobot/greenDAO
greenDAO 是一款开源的,针对 Android 操作 SQLite 的 ORM 框架。它将 Java 对象映射到 SQLite 数据库中,使我们在操作数据库的时候不用编写 SQL 语句即可操纵数据库。
首先在项目的 build.gradle 文件的 dependencies 中添加
buildscript {
repositories {
google()
jcenter()
mavenCentral() // add repository
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
……
}
}
然后在 module 的 build.gradle 中添加
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugin
……
dependencies {
implementation 'org.greenrobot:greendao:3.2.2'
}
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties {*;}
# If you do not use SQLCipher:
-dontwarn net.sqlcipher.database.**
# If you do not use RxJava:
-dontwarn rx.**
在 module 的 gradle 中添加
android {
……
greendao {
schemaVersion 1 //数据库版本号,迁移的时候用
daoPackage ‘com.example.test.greendao.gen’ //指定DaoMaster、DaoSession、【实体名】Dao 的包名
targetGenDir 'src/main/java' //指定目标路径
}
……
}
@Entity(indexes = { @Index(value = "text, date DESC", unique = true)})
public class Note {
@Id(autoincrement = true) //该字段为主键,自增
private Long id;
@NotNull //字段不允许为空
private String text;
private Date date;
@Convert(converter = RecordConverter.class, columnType = Integer.class) //自定义转换器
private RecordMode mode;
@Transient //该字段不保存到数据库
private float noUse;
}
Alt + Insert
快捷键,选择自动生成 Getter and Setter
和 toString
(用来打印查询结果)Ctrl + F9
),然后就会在配置的包名下生成对应的 DaoMaster、DaoSession、【实体名】Dao。下面是一个枚举类型的自定义类,可以是任意类
/**
* 记录模式
*/
public enum RecordMode {
/** 正在编写 */
Draft(0),
/** 已保存 */
Saved(1),
/** 已上传 */
Uploaded(2);
private int value;
RecordMode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static RecordMode newInstance(int value) {
RecordMode[] modes = values();
for(RecordMode mode : modes) {
if(mode.value == value) {
return mode;
}
}
return RecordMode.Draft;
}
}
然后构造一个转化器进行转化
public class RecordConverter implements PropertyConverter {
@Override
public RecordMode convertToEntityProperty(Integer databaseValue) {
return RecordMode.newInstance(databaseValue);
}
@Override
public Integer convertToDatabaseValue(RecordMode entityProperty) {
return entityProperty.getValue();
}
}
最后在实体的属性上添加上 @Convert 注解即可
@Convert(converter = RecordConverter.class, columnType = Integer.class)
private RecordMode mode;
对于 Date 类型,虽然它不是 SQLite 支持的几种基本类型,但是它是Java中的类型,GreenDAO对它自动做了转换,将它转为 Integer 类型。
@Entity:表明这个实体类会在数据库中生成一个与之相对应的表。
初始化 GreenDao 就是 初始化一个 DaoSession,该操作通常只会在整个 App 的生命周期中被调用一次。因此,我们通常把它放在 Application 里面或者使用惰性加载或单例(即当第一次使用数据库的时候才打开)
注意:“不加密数据库” 和 “加密数据库” 不能使用同一个名字,因为加密是对整个数据库加密,名字相同则路径相同是同一个文件,但两者一个不需要解密,一个要解密,会报文件不是数据库的错误。所以要分开定义。
private DaoSession daoSession;
private DaoSession daoEncryptSession;
private void initGreenDAO(Context context, String password) {
//未加密的数据库
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, "RawDb");
Database db = helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();
//加密的数据库
DaoMaster.DevOpenHelper encryptHelper = new DaoMaster.DevOpenHelper(context, "EncryptDb");
Database encryptDb = encryptHelper.getEncryptedWritableDb(password);
daoEncryptSession = new DaoMaster(encryptDb).newSession();
}
public DaoSession getDaoSession() {
return daoSession;
}
public DaoSession getDaoEncryptSession() {
return daoEncryptSession;
}
后续的操作可以使用 DaoSession 直接调用,也可以通过 DaoSession 拿到相应的 Dao 再去调用,两者是一样的。
拿 insert 举个例子,在 AbstractDaoSession.java 的源码中是这样调用的
public long insert(T entity) {
@SuppressWarnings("unchecked")
// Dao的第一个参数是"实体"类型,第二个参数是"主键"类型
AbstractDao dao = (AbstractDao) getDao(entity.getClass());
return dao.insert(entity);
}
public AbstractDao, ?> getDao(Class extends Object> entityClass) {
// entityToDao 是一个 map,通过实体类映射Dao元素,如果找到则返回
AbstractDao, ?> dao = entityToDao.get(entityClass);
if (dao == null) {
throw new DaoException("No DAO registered for " + entityClass);
}
return dao;
}
所以说,操作 DaoSession 来执行增删改查,实际上就是操作对应的 Dao,只是 GreenDAO 帮我们封装了一层而已。
insert(T entity)
将对象插入数据库insertInTx(Iterable entities)
批量插入数据库insertInTx(T... entities)
批量插入数据库insertInTx(Iterable entities, boolean setPrimaryKey)
批量插入数据库(主键用实体类中定义的,或数据库自动生成)insertOrReplaceInTx(Iterable entities, boolean setPrimaryKey)
批量插入数据库(主键用实体类中定义的,或数据库自动生成)insertOrReplaceInTx(Iterable entities)
批量插入数据库,如果已存在则替换该项insertOrReplaceInTx(T... entities)
批量插入数据库,如果已存在则替换该项delete(T entity)
删除单个数据deleteInTx(Iterable entities)
批量删除数据deleteInTx(T... entities)
批量删除数据deleteByKey(K key)
通过主键删除数据deleteByKeyInTx(Iterable entities)
通过主键批量删除数据deleteByKeyInTx(K... keys)
通过主键批量删除数据deleteAll()
删除所有数据update(T entity)
根据主键修改实体updateInTx(Iterable entities)
批量修改实体updateInTx(T... entities)
批量修改实体修改这里有的特殊,通过源码可以看的 where 语句里用的是 pkColumns,pk指的是主键(一般是Long类型的 Id),因此如果直接用上述的方法去修改,那么之前就必须先查询一次,把待修改的字段提出来,然后再根据 Id 再去数据库修改该条字段。
T load(K key)
通过主键查询实体List loadAll()
查询所有元素T loadByRowId(long rowId)
通过行号获取元素QueryBuilder queryBuilder()
创建查询构造器List queryRaw(String where, String... selectionArg)
使用 sql 的 where 语句进行查询Query queryRawCreate(String where, Object... selectionArg)
使用 sql 的 where 语句创建查询对象,可用于拼接Query queryRawCreateListArgs(String where, Collection
使用 sql 的 where 语句创建查询对象,可用于拼接void refresh(T entity)
根据主键,从数据库中重新加载实体执行数据库语句需要和创建数据库时的线程保持一致,否则会报错,因此 Query 就有个 forCurrentThread,方法可以让查询在原线程调用。
public Query forCurrentThread() {
return queryData.forCurrentThread(this);
}
有时候需求比较复杂,GreenDAO 提供的 api 实现不了你的需求,这时候就需要用到 sql 语句了(该情况主要增对:增、删、改;至于查询,GreenDAO 原生的方法就够用了)
daoSession.getDatabase().execSQL(sql)
简单的增删改,可以借助 SqlUtils.java 这个 GreenDAO 的工具类来实现,就不用重新造轮子了。
例如如果除 Id 外,还有其它固定不变的唯一标识,就可以在不查询的前提下,直接修改,这样就省了一次操作数据库的步骤
GreenDAO 自带的 OpenHelper 只是一个用于开发版本的数据库辅助工具,它在发现数据库版本升级时,会删除所有的数据库,然后重新创建。
所以在实际生产环境,需要继承并重写 DaoMaster.OpenHelper 类的 onUpgrade 方法。
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
for (int j = oldVersion + 1; j <= newVersion; j++) {
switch (j) {
case 2:
break;
case 3:
break;
default:
break;
}
}
}
也可以使用升级辅助库 GreenDaoUpgradeHelper 类。
原理是:通过 MigrationHelper 在删表重建的过程中,使用临时表保存数据并还原,表格重建之后自动拼接字段名并填入值,然后批量重新插入新建的表中。
MigrationHelper 的 Github 地址 https://github.com/yuweiguocn/GreenDaoUpgradeHelper
在项目根路径的 build.gradle 中添加
allprojects {
repositories {
google()
jcenter()
// 用来引入 rxpermission, GreenDaoUpgradeHelper 等第三方库
maven { url 'https://jitpack.io' }
}
}
在 module 目录的 build.gradle 中添加
dependencies {
……
implementation 'org.greenrobot:greendao:3.2.2'
implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1'
}
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static void dropTable(org.greenrobot.greendao.database.Database, boolean);
public static void createTable(org.greenrobot.greendao.database.Database, boolean);
}
创建 MySQLiteOpenHelper.java,并添加如下内容
public class MySQLiteOpenHelper extends DaoMaster.OpenHelper {
public MySQLiteOpenHelper(Context context, String name) {
super(context, name);
}
public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {
@Override
public void onCreateAllTables(Database db, boolean ifNotExists) {
DaoMaster.createAllTables(db, ifNotExists);
}
@Override
public void onDropAllTables(Database db, boolean ifExists) {
DaoMaster.dropAllTables(db, ifExists);
}
}, NoteDao.class);
}
}
migrate 函数的最后是实体类对应的 dao 的类型,有多少个实体类,这里就要加多少个 dao
migrate 函数原型如下
public static void migrate(Database database, ReCreateAllTableListener listener, Class extends AbstractDao, ?>>... daoClasses) {
weakListener = new WeakReference<>(listener);
migrate(database, daoClasses);
}
最后修改原先创建数据库时使用的 helper 即可
private void initGreenDAO(String password) {
//未加密的数据库
// DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "RawDb");
MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this, "RawDb");
Database db = helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();
//加密的数据库
// DaoMaster.DevOpenHelper encryptHelper = new DaoMaster.DevOpenHelper(this, "EncryptDb");
MySQLiteOpenHelper encryptHelper = new MySQLiteOpenHelper(this, "EncryptDb");
Database encryptDb = encryptHelper.getEncryptedWritableDb(password);
daoEncryptSession = new DaoMaster(encryptDb).newSession();
}
inert into table1 ( field1, field2 ) values ( value1, value2 )
delete from table1 where 【筛选条件】
update table1 set field1=value1, field2=value2 where 【筛选条件】
select * from table1 where 【筛选条件】
select * from table1 where field1 like '%value1%'
select * from table1 order by field1, field2 【ASC/DESC】
select count as total from table1
select sum(field1) as sumField1 from table1
select avg(field1) as avgField1 from table1
select max(field1) as maxField1 from table1
select min(field1) as minField1 from table1
调用 Database db = openHelper.getEncryptedWritableDb(password);
时报错,找不到对应的类,而获取普通未加密的数据库则一切正常。
(测试机是 vivo X20A,Android 8.1.0)
java.lang.NoClassDefFoundError: org.greenrobot.greendao.database.DatabaseOpenHelper$EncryptedHelper
at org.greenrobot.greendao.database.DatabaseOpenHelper.checkEncryptedHelper(DatabaseOpenHelper.java:121)
at org.greenrobot.greendao.database.DatabaseOpenHelper.getEncryptedWritableDb(DatabaseOpenHelper.java:133)
因为在 Android5.0 之前,每一个 android 应用中只会含有一个 dex 文件,但是这个 dex 的方法数量被限制在65535之内,这就是著名的 64K(64*1024) 事件。为了解决这个问题,Google 官方推出了这个类似于补丁一样的 support-library, MultiDex。
解决方法如下
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}
因为我的项目已经是基于 androidx 的了所以引用的都是 androidx 的包,如果你的项目还是基于 android support,请使用 'com.android.support:multidex:1.0.3'
...
public class MyApplication extends MultiDexApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
如果是 Android 5.0 以上的,那就是没加加密数据的模块了,在 module 的 build.gradle 上加上即可
dependencies {
//数据库加密
implementation 'net.zetetic:android-database-sqlcipher:4.2.0'
}