GreenDao是一款开源的面向Android的轻便、快捷的ORM(对象映射)框架,将Java对象映射到SQLite数据库中,避免编写复杂的SQL语句。具有高性能、低开销,且支持数据库加密等功能。本文只做GreenDao核心API简析,详细使用请参考GreenDao官方文档译文 ,ORM数据库框架greenDAO SQLite MD。
相关概念
核心类
- DaoMaster:负责管理数据库对象(SQLiteDatabase)和DAO类(对象),通过OpenHelper、DevOpenHelper和SQLiteOpenHelper创建不同模式的SQLite数据库;
- DaoSession:管理指定模式下的所有DAO对象,提供了基本的实体类操作;
- XxxDAO:针对每个实体类生成的数据访问对象;
- Entities:可持久化对象,一个JavaBean实例对应数据库表的一行;
注解
- @Entity:表明这个实体类会在数据库中生成一个与之对应的表;注:Kotlin不支持。
- @Id:对应数据库表中的id字段,是一条数据的唯一标识,必须是Long类型;@Id(autoincrement = true)表示自增型主键。
- @Property(nameInDb = "name"):表明这个属性对应数据库表中的name字段;
- @NotNull:表明该属性值不能为空;
- @Transient:表明该属性值不被存储在数据库中;
- @Unique:表明该属性在数据库中只能有唯一值, 也可作为一条数据的唯一标识;优先级高于主键Id。
- 多个@Unique注解的数据,满足其中一个就认定是同一条数据。
- 自增型主键时,以@Unique注解的值为唯一性标识;
- @OrderBy:增加某一字段的排序,如@OrderBy("data ASC") 正序;
常用API
- QueryBuilder:SQL的语法错误只有在runtime时才会提示,但QueryBuilder可以在编译期就检测到错误。
- where:配置查询条件,可传递多个查询条件。where语句里的条件用“且”连接,whereOr语句里的条件用"或"连接。
- limit(int):从前面限制获取的条数。
- offset(int):从某位置开始获取数据,即数据返回的偏移量。需要结合limit使用。
- eq:等于;notEq:不等于;like:模糊查询;gt:大于;ge:大于等于;
- insert:插入数据,如果已存在则插入失败,insertInTx是在事务中操作;
- insertOrReplace:插入数据,如果已存在则替换(判断一条数据是否存在,由主键id或@Unique(高优先级)作为唯一标识),insertOrReplaceInTx是在事务中操作。
环境配置
- module级别的build.gradle里:
apply plugin: 'org.greenrobot.greendao'
greendao {
schemaVersion 1 //数据库版本号
daoPackage 'com.example.myapplication.db'// 设置DaoMaster、DaoSession、Dao 包名
targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录
}
dependencies {
implementation 'org.greenrobot:greendao:3.2.2'//数据库
implementation 'net.zetetic:android-database-sqlcipher:4.2.0'//数据库加密
implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1'//数据库升级
}
- Project级别的build.gradle里:
dependencies {
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
}
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
核心功能
加密
SQLCipher是在SQLite基础上自定义256位的AES加密开源数据库,加密性能高,开销低。
public class App extends Application {
public static DaoSession daoSession;
private static String dbPassword = "test";//推荐使用设备唯一标识类UUID的字段加密值。
@Override
public void onCreate() {
super.onCreate();
initGreenDao();
}
private void initGreenDao() {
DaoMaster.DevOpenHelper helper = new DbOpenHelper(this, "vivo_test.db", null);
daoSession = new DaoMaster(helper.getEncryptedWritableDb(dbPassword)).newSession();
//要在debug模式下开启,输出带有具体数值的SQL日志
QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;
}
}
SQLCipher是应用级别的数据库加密,通过命令查询等操作数据库,会被提示数据库加密,操作失败,进一步保证了数据的安全性。
SQLCipher的so包会增加apk的大小,约1.4M,不过平衡程序的安全性,影响不大。
升级
GreenDao在数据库版本升级时,默认会删除低版本数据。目前主流的方案是在数据库升级时将低版本数据迁移到临时表中,成功插入新版本的表后,再删除低版本数据。
新建 DbOpenHelper类继承自 DaoMaster.DevOpenHelper,并重写 onUpgrade方法,通过 GreenDaoUpgradeHelper库内的MigrationHelper类配置好所有DAO类,如PersonBeanDao.class、BankCardDao.class类,即可实现数据的迁移。
之后更改build.gradle中GreenDao的数据库版本号,clean Project -> makeProject。
public class DbOpenHelper extends DaoMaster.DevOpenHelper {
public DbOpenHelper(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.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);
}
}, PersonBeanDao.class,BankCardDao.class);
}
}
在 onUpgrade中处理数据迁移时,要注释掉 super.onUpgrade(db, oldVersion, newVersion); 并将现有所有的DAO类都加到 MigrationHelper.migrate()的参数内。
多表关联
@ToOne 定义了一个entities与另一个entities的1:1对应关系。通过joinProperty参数定义外键。
@ToMany 定义了一个entities与另一个entities的1:N对应关系。通过referencedJoinProperty参数定义外键。
- 1:1对应关系示例
//源实体
@Entity
puiblic class PersonBean {
@Id
puiblic Long id;
public String name;
public Long bankCardId;
@ToOne(joinProperty = "bankCardId")
public BankCardBean bankCardBeans;
...
}
//目标实体
@Entity
public class BankCardBean {
@Id
public long id;
public float money;
}
一对一表格关联,源实体与目标实体通过@ToOne(joinProperty= "bankCardId")外键关联,源实体中的bankCardId值关联目标实体的主键id。
- 1:N对应关系示例
//源实体
@Entity
puiblic class PersonBean {
@Id
puiblic Long id;
public String name;
@ToMany(referencedJoinProperty = "personId")
@OrderBy("money ASC")
public List bankCardBeans;
...
}
//目标实体
@Entity
public class BankCardBean {
@Id
public Long id;
public float money;
public long personId;
}
在目标实体中定义与源实体关联的外键,即BankCardBean的personId,在源实体中由referencedJoinProperty 指定目标实体的外键。
目前GreenDao的多表关联方案较原始,不支持级联删除。不过目前项目中此类业务不多,可分别处理关联表的数据操作。
操作示例
private void asyncInsert() {
final AsyncSession asyncSession = CommonInit.daoSession.startAsyncSession();
asyncSession.runInTx(new Runnable() {
@Override
public void run() {
asyncSession.setListenerMainThread(new AsyncOperationListener() {
@Override
public void onAsyncOperationCompleted(AsyncOperation operation) {
if (operation.isCompletedSucessfully()) {
//主线程 判断数据库操作完毕 更新UI
}
}
});
//todo 子线程 在此处开启事务操作数据库
App.daoSession.getPersonBeanDao().insertOrReplaceInTx(personBeans);
}
});
}
为了性能最优化,宜采用异步操作数据库。事务除了可以确保数据处理的动作的完整性,还能提升大量插入数据的性能。
注意:以下数据库操作相比示例只是替换了具体操作。
- 增
CommonInit.daoSession.getPersonBeanDao().insertOrReplaceInTx(personBeans);
- 改
CommonInit.daoSession.getPersonBeanDao().updateInTx(personBeans);
- 查
示例:查询该账号下年龄大于29岁的人,并按照年龄正序输出从第五个开始的前20个人。多个检索条件,多个where。
final List personBeans =
CommonInit.daoSession.getPersonBeanDao().queryBuilder()
.where(PersonBeanDao.Properties.Age.gt(29))
.offset(5)
.limit(20)
.orderAsc(PersonBeanDao.Properties.Age)
.list();
等同于
final List personBeans =
CommonInit.daoSession.getPersonBeanDao().queryRaw(
"WHERE AGE>? ORDER BY AGE ASC LIMIT ? OFFSET ?", "221239", "20", "5");
- 删
CommonInit.daoSession.getPersonBeanDao().deleteInTx(personBeans);
- 实体类
@Entity
public class PersonBean implements Serializable {
public static final long serialVersionUID = 1234134312341234L;
@Id(autoincrement = true)
public Long id;//一般不参与业务,自增主键
@Unique
public String identifyId;
public String firstname;
public String secondname;
public String age;
@Transient
public String address;
...
}