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

短命女 2023-08-17 16:46 151阅读 0赞

定义

greenDAO 官网:http://greenrobot.org/greendao/
greenDAO 的 Github 地址:https://github.com/greenrobot/greenDAO

greenDAO 是一款开源的,针对 Android 操作 SQLite 的 ORM 框架。它将 Java 对象映射到 SQLite 数据库中,使我们在操作数据库的时候不用编写 SQL 语句即可操纵数据库。

优点

  • 轻量级
  • 支持缓存
  • 支持数据库加密
  • 通过对象映射,操作简便
  • 采用预处理,自动生成代码,而非反射,速度快效率高

添加依赖

首先在项目的 build.gradle 文件的 dependencies 中添加

  1. buildscript {
  2. repositories {
  3. google()
  4. jcenter()
  5. mavenCentral() // add repository
  6. }
  7. dependencies {
  8. classpath 'com.android.tools.build:gradle:3.1.1'
  9. classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
  10. ……
  11. }
  12. }

然后在 module 的 build.gradle 中添加

  1. apply plugin: 'com.android.application'
  2. apply plugin: 'org.greenrobot.greendao' // apply plugin
  3. ……
  4. dependencies {
  5. implementation 'org.greenrobot:greendao:3.2.2'
  6. }

R8 或 ProGuard 混淆配置

  1. -keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
  2. public static java.lang.String TABLENAME;
  3. }
  4. -keep class **$Properties {*;}
  5. # If you do not use SQLCipher:
  6. -dontwarn net.sqlcipher.database.**
  7. # If you do not use RxJava:
  8. -dontwarn rx.**

配置版本

在 module 的 gradle 中添加

  1. android {
  2. ……
  3. greendao {
  4. schemaVersion 1 //数据库版本号,迁移的时候用
  5. daoPackage com.example.test.greendao.gen //指定DaoMaster、DaoSession、【实体名】Dao 的包名
  6. targetGenDir 'src/main/java' //指定目标路径
  7. }
  8. ……
  9. }

添加测试模型

  1. 创建 Note.java

    @Entity(indexes = { @Index(value = “text, date DESC”, unique = true)})
    public class Note {

    1. @Id(autoincrement = true) //该字段为主键,自增
    2. private Long id;
    3. @NotNull //字段不允许为空
    4. private String text;
    5. private Date date;
    6. @Convert(converter = RecordConverter.class, columnType = Integer.class) //自定义转换器
    7. private RecordMode mode;
    8. @Transient //该字段不保存到数据库
    9. private float noUse;

    }

  2. 按下 Alt + Insert 快捷键,选择自动生成 Getter and SettertoString(用来打印查询结果)

  3. 编译项目(Make Project,Ctrl + F9),然后就会在配置的包名下生成对应的 DaoMaster、DaoSession、【实体名】Dao。
    (注意:一定要按顺序操作,先创建实体类,然后编译才会生成相应文件)
    在这里插入图片描述

自定义类型

下面是一个枚举类型的自定义类,可以是任意类

  1. /**
  2. * 记录模式
  3. */
  4. public enum RecordMode {
  5. /** 正在编写 */
  6. Draft(0),
  7. /** 已保存 */
  8. Saved(1),
  9. /** 已上传 */
  10. Uploaded(2);
  11. private int value;
  12. RecordMode(int value) {
  13. this.value = value;
  14. }
  15. public int getValue() {
  16. return value;
  17. }
  18. public static RecordMode newInstance(int value) {
  19. RecordMode[] modes = values();
  20. for(RecordMode mode : modes) {
  21. if(mode.value == value) {
  22. return mode;
  23. }
  24. }
  25. return RecordMode.Draft;
  26. }
  27. }

然后构造一个转化器进行转化

  1. public class RecordConverter implements PropertyConverter<RecordMode, Integer> {
  2. @Override
  3. public RecordMode convertToEntityProperty(Integer databaseValue) {
  4. return RecordMode.newInstance(databaseValue);
  5. }
  6. @Override
  7. public Integer convertToDatabaseValue(RecordMode entityProperty) {
  8. return entityProperty.getValue();
  9. }
  10. }

最后在实体的属性上添加上 @Convert 注解即可

  1. @Convert(converter = RecordConverter.class, columnType = Integer.class)
  2. 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 里面或者使用惰性加载或单例(即当第一次使用数据库的时候才打开)

注意:“不加密数据库” 和 “加密数据库” 不能使用同一个名字,因为加密是对整个数据库加密,名字相同则路径相同是同一个文件,但两者一个不需要解密,一个要解密,会报文件不是数据库的错误。所以要分开定义。

  1. private DaoSession daoSession;
  2. private DaoSession daoEncryptSession;
  3. private void initGreenDAO(Context context, String password) {
  4. //未加密的数据库
  5. DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, "RawDb");
  6. Database db = helper.getWritableDb();
  7. daoSession = new DaoMaster(db).newSession();
  8. //加密的数据库
  9. DaoMaster.DevOpenHelper encryptHelper = new DaoMaster.DevOpenHelper(context, "EncryptDb");
  10. Database encryptDb = encryptHelper.getEncryptedWritableDb(password);
  11. daoEncryptSession = new DaoMaster(encryptDb).newSession();
  12. }
  13. public DaoSession getDaoSession() {
  14. return daoSession;
  15. }
  16. public DaoSession getDaoEncryptSession() {
  17. return daoEncryptSession;
  18. }

调用方式

后续的操作可以使用 DaoSession 直接调用,也可以通过 DaoSession 拿到相应的 Dao 再去调用,两者是一样的。

拿 insert 举个例子,在 AbstractDaoSession.java 的源码中是这样调用的

  1. public <T> long insert(T entity) {
  2. @SuppressWarnings("unchecked")
  3. // Dao的第一个参数是"实体"类型,第二个参数是"主键"类型
  4. AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
  5. return dao.insert(entity);
  6. }
  7. public AbstractDao<?, ?> getDao(Class<? extends Object> entityClass) {
  8. // entityToDao 是一个 map,通过实体类映射Dao元素,如果找到则返回
  9. AbstractDao<?, ?> dao = entityToDao.get(entityClass);
  10. if (dao == null) {
  11. throw new DaoException("No DAO registered for " + entityClass);
  12. }
  13. return dao;
  14. }

所以说,操作 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,方法可以让查询在原线程调用。

  1. public Query<T> forCurrentThread() {
  2. return queryData.forCurrentThread(this);
  3. }

使用sql语句

有时候需求比较复杂,GreenDAO 提供的 api 实现不了你的需求,这时候就需要用到 sql 语句了(该情况主要增对:增、删、改;至于查询,GreenDAO 原生的方法就够用了)

daoSession.getDatabase().execSQL(sql)

简单的增删改,可以借助 SqlUtils.java 这个 GreenDAO 的工具类来实现,就不用重新造轮子了。
例如如果除 Id 外,还有其它固定不变的唯一标识,就可以在不查询的前提下,直接修改,这样就省了一次操作数据库的步骤
在这里插入图片描述

更新数据库

GreenDAO 自带的 OpenHelper 只是一个用于开发版本的数据库辅助工具,它在发现数据库版本升级时,会删除所有的数据库,然后重新创建。
在这里插入图片描述

原生方式

所以在实际生产环境,需要继承并重写 DaoMaster.OpenHelper 类的 onUpgrade 方法。

  1. @Override
  2. public void onUpgrade(Database db, int oldVersion, int newVersion) {
  3. for (int j = oldVersion + 1; j <= newVersion; j++) {
  4. switch (j) {
  5. case 2:
  6. break;
  7. case 3:
  8. break;
  9. default:
  10. break;
  11. }
  12. }
  13. }

升级辅助库 GreenDaoUpgradeHelper

也可以使用升级辅助库 GreenDaoUpgradeHelper 类。

原理是:通过 MigrationHelper 在删表重建的过程中,使用临时表保存数据并还原,表格重建之后自动拼接字段名并填入值,然后批量重新插入新建的表中。

MigrationHelper 的 Github 地址 https://github.com/yuweiguocn/GreenDaoUpgradeHelper

添加依赖

在项目根路径的 build.gradle 中添加

  1. allprojects {
  2. repositories {
  3. google()
  4. jcenter()
  5. // 用来引入 rxpermission, GreenDaoUpgradeHelper 等第三方库
  6. maven { url 'https://jitpack.io' }
  7. }
  8. }

在 module 目录的 build.gradle 中添加

  1. dependencies {
  2. ……
  3. implementation 'org.greenrobot:greendao:3.2.2'
  4. implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1'
  5. }

混淆规则

  1. -keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
  2. public static void dropTable(org.greenrobot.greendao.database.Database, boolean);
  3. public static void createTable(org.greenrobot.greendao.database.Database, boolean);
  4. }

代码实现

创建 MySQLiteOpenHelper.java,并添加如下内容
在这里插入图片描述

  1. public class MySQLiteOpenHelper extends DaoMaster.OpenHelper {
  2. public MySQLiteOpenHelper(Context context, String name) {
  3. super(context, name);
  4. }
  5. public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
  6. super(context, name, factory);
  7. }
  8. @Override
  9. public void onUpgrade(Database db, int oldVersion, int newVersion) {
  10. MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {
  11. @Override
  12. public void onCreateAllTables(Database db, boolean ifNotExists) {
  13. DaoMaster.createAllTables(db, ifNotExists);
  14. }
  15. @Override
  16. public void onDropAllTables(Database db, boolean ifExists) {
  17. DaoMaster.dropAllTables(db, ifExists);
  18. }
  19. }, NoteDao.class);
  20. }
  21. }

migrate 函数的最后是实体类对应的 dao 的类型,有多少个实体类,这里就要加多少个 dao

migrate 函数原型如下

  1. public static void migrate(Database database, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {
  2. weakListener = new WeakReference<>(listener);
  3. migrate(database, daoClasses);
  4. }

最后修改原先创建数据库时使用的 helper 即可

  1. private void initGreenDAO(String password) {
  2. //未加密的数据库
  3. // DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "RawDb");
  4. MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this, "RawDb");
  5. Database db = helper.getWritableDb();
  6. daoSession = new DaoMaster(db).newSession();
  7. //加密的数据库
  8. // DaoMaster.DevOpenHelper encryptHelper = new DaoMaster.DevOpenHelper(this, "EncryptDb");
  9. MySQLiteOpenHelper encryptHelper = new MySQLiteOpenHelper(this, "EncryptDb");
  10. Database encryptDb = encryptHelper.getEncryptedWritableDb(password);
  11. daoEncryptSession = new DaoMaster(encryptDb).newSession();
  12. }

常用的 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)

  1. java.lang.NoClassDefFoundError: org.greenrobot.greendao.database.DatabaseOpenHelper$EncryptedHelper
  2. at org.greenrobot.greendao.database.DatabaseOpenHelper.checkEncryptedHelper(DatabaseOpenHelper.java:121)
  3. 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 {

    1. 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”?>





  2. 自定义的 Application 继承自 MultiDexApplication,重写 Application的attachBaseContext()这个方法。

    public class MyApplication extends MultiDexApplication {

    1. @Override
    2. protected void attachBaseContext(Context base) {
    3. super.attachBaseContext(base);
    4. MultiDex.install(this);
    5. }

    }

如果是 Android 5.0 以上的,那就是没加加密数据的模块了,在 module 的 build.gradle 上加上即可

  1. dependencies {
  2. //数据库加密
  3. implementation 'net.zetetic:android-database-sqlcipher:4.2.0'
  4. }

发表评论

表情:
评论列表 (有 0 条评论,151人围观)

还没有评论,来说两句吧...

相关阅读

    相关 ElasticSearch5.X 增删

    ES支持近实时的索引、更新、查询、删除文档,近实时就意味着刚刚索引的数据需要1秒钟后才能搜索到,这也是与传统的SQL数据库不同的地方。 索引/替换文档 之前已经试过

    相关 JDBC进行增删

    JDBC进行增删改查 本篇博客较为基础,只作学习记录之用。 本篇分为: 下载JDBC的jar包 导入JDBC的jar包 封装数据库连接和关闭