Android 使用 GreenDAO 3.x 进行增删改查和升级

Posted 福州-司马懿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 使用 GreenDAO 3.x 进行增删改查和升级相关的知识,希望对你有一定的参考价值。

定义

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'

R8 或 ProGuard 混淆配置

-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'	//指定目标路径
	
	……

添加测试模型

  1. 创建 Note.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;

  1. 按下 Alt + Insert 快捷键,选择自动生成 Getter and SettertoString(用来打印查询结果)
  2. 编译项目(Make Project,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<RecordMode, Integer> 

    @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:表明这个实体类会在数据库中生成一个与之相对应的表。

  • nameInDb:表名默认为实体名,通过该变量可以自定义表名
  • indexes:定义索引,多个列作为索引可以在字符串中用逗号分隔。unique 标识索引是否唯一。
  • createInDb:如果有多个实体关联同一张表,可以在其它实体中将的该变量设为false,来避免表的重复创建(默认为 true)
  • schema:当一个项目有多个数据库时,要标明这个dao属于哪个schema(schema即封装后的数据库)
  • active:是否应该生成更新/删除/刷新方法。如果 Entity 定义了 @ToOne 或 @ToMany 关系,那么该值有效,指是否支持实体类之间的 update,refresh,delete 等操作

属性注解

  • @Id:对应数据库表中的主键,是每条数据的唯一标识,可以通过设置 autoincrement 来使其自增。如果实体中没有声明主键,会默认创建一个Long类型的自增的主键 “_id”。
    GreenDAO在每次启动应用时,会初始化ID为1,如果没加 autoincrement = true,第二次启动再 insert 就会报错。
  • @Property:可以通过设置nameInDb,来指定该字段在数据库中的
  • @NotNull:值不能为空
  • @Transient:短暂的,标识该属性不会被存入数据库中
  • @Unique:表明该属性的值在数据库中必须唯一。
  • @Index:创建一个索引,通过name设置索引的别名,也可以通过unique给所有添加约束
  • @Convert:指定一个PropertyConverter,用于支持自定义类型和sqlite支持的类型之间的转化

关系注解

  • @ToOne:定义自己与一个实体对象的关系
  • @ToMany:定义自己与多个实体对象的关系。
  • @JoinProperty:对于更复杂的关系,可以使用这个注解标明目标属性的源属性
  • @JoinEntity:多对多关系,有其它的表或实体时,可以给目标属性添加这个额外的注解
  • @OrderBy:默认按主键ASC升序排列

派生注解

  • @Generated:这个是编译之后 GreenDAO 自动生成的,注解内包含一个哈希值,来标识该方法的唯一性。它会自动生成实体类的 “无参构造函数” 和 “全参构造函数”。如果该函数已存在,则不会添加注解,否则自动生成并添加注解。

数据库操作

初始化

初始化 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 <T> long insert(T entity) 
    @SuppressWarnings("unchecked")
    // Dao的第一个参数是"实体"类型,第二个参数是"主键"类型
    AbstractDao<T, ?> dao = (AbstractDao<T, ?>) 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<T> entities) 批量插入数据库
  • insertInTx(T... entities) 批量插入数据库
  • insertInTx(Iterable<T> entities, boolean setPrimaryKey) 批量插入数据库(主键用实体类中定义的,或数据库自动生成)
  • insertOrReplaceInTx(Iterable<T> entities, boolean setPrimaryKey) 批量插入数据库(主键用实体类中定义的,或数据库自动生成)
  • insertOrReplaceInTx(Iterable<T> entities) 批量插入数据库,如果已存在则替换该项
  • insertOrReplaceInTx(T... entities) 批量插入数据库,如果已存在则替换该项

  • delete(T entity) 删除单个数据
  • deleteInTx(Iterable<T> entities) 批量删除数据
  • deleteInTx(T... entities) 批量删除数据
  • deleteByKey(K key) 通过主键删除数据
  • deleteByKeyInTx(Iterable<T> entities) 通过主键批量删除数据
  • deleteByKeyInTx(K... keys) 通过主键批量删除数据
  • deleteAll() 删除所有数据

  • update(T entity) 根据主键修改实体
  • updateInTx(Iterable<T> entities) 批量修改实体
  • updateInTx(T... entities) 批量修改实体

修改这里有的特殊,通过源码可以看的 where 语句里用的是 pkColumns,pk指的是主键(一般是Long类型的 Id),因此如果直接用上述的方法去修改,那么之前就必须先查询一次,把待修改的字段提出来,然后再根据 Id 再去数据库修改该条字段。

  • T load(K key) 通过主键查询实体
  • List<T> loadAll() 查询所有元素
  • T loadByRowId(long rowId) 通过行号获取元素
  • QueryBuilder<T> queryBuilder() 创建查询构造器
  • List<T> queryRaw(String where, String... selectionArg) 使用 sql 的 where 语句进行查询
  • Query<T> queryRawCreate(String where, Object... selectionArg) 使用 sql 的 where 语句创建查询对象,可用于拼接
  • Query<T> queryRawCreateListArgs(String where, Collection<Object> selectionArg) 使用 sql 的 where 语句创建查询对象,可用于拼接
  • void refresh(T entity) 根据主键,从数据库中重新加载实体

执行数据库语句需要和创建数据库时的线程保持一致,否则会报错,因此 Query 就有个 forCurrentThread,方法可以让查询在原线程调用。

public Query<T> forCurrentThread() 
   return queryData.forCurrentThread(this);

使用sql语句

有时候需求比较复杂,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

也可以使用升级辅助库 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();

常用的 sql 语句

  • 增加
    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%'
  • 排序(ASC升序 / DESC降序)
    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。

解决方法如下

  1. 在 module 的 build.gradle 文件中加上
dependencies 
	implementation 'androidx.multidex:multidex:2.0.1'

因为我的项目已经是基于 androidx 的了所以引用的都是 androidx 的包,如果你的项目还是基于 android support,请使用 'com.android.support:multidex:1.0.3'

  1. 在 manifest.xml 中声明自定义的 Application
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.test">
<application
...
android:name="com.example.global.MyApplicatoin">
...
</application>
</manifest>
  1. 自定义的 Application 继承自 MultiDexApplication,重写 Application的attachBaseContext()这个方法。
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'

以上是关于Android 使用 GreenDAO 3.x 进行增删改查和升级的主要内容,如果未能解决你的问题,请参考以下文章

greenDao:操作数据库的开源框架

android greenDao使用

「Android」GreenDao

Android:安卓学习笔记之GreenDao 的简单理解和使用

Android GreenDao使用教程

GreenDao在Android项目中的实践总结