Android探坑集锦<一>数据库之greendao

前段时间工作中接触到了数据库greendao,将项目中所有原生sqlite替换成为了greendao数据库封装框架,对于一些经验和坑进行总结和记录。

一、android原生sqlite

我们一般用Android sqlite无非两种。

1.使用原生的sqlitehelper

简单的使用SQLiteOpenHelper与数据库建立连接,获取到SQLiteDatabase对象,然后就可以进行增删改查操作

Android探坑集锦<一>数据库之greendao_第1张图片

2.对于helper做一层封装,使用自定义contentProvider进行查询,查询的时候传入uri,使用UriMatcher根据不同的uri匹配到不同的uri code

(对于UriMatcher的匹配规则,在这里我们啰嗦一下)

Android探坑集锦<一>数据库之greendao_第2张图片
这三条语句的uri分别匹配下面定义规则的1、2、3
Android探坑集锦<一>数据库之greendao_第3张图片
其中#表示匹配任意长度的数字、*表示匹配任意长度的任意字符

二、原生sqlite数据变化通知界面

1.注册数据库的监听

注册监听contentObserver,注意要unregister

2.使用cursorloader

Android探坑集锦<一>数据库之greendao_第4张图片
cursorloader举栗

初始化并启动Loader,会在createLoader方法中回调我们的onCreateLoader,我们在这里生成一个CursorLoader对象,其中设置了要查询的条件。在CursorLoader的onForceLoad实现中有一个异步任务,这个异步任务的loadInBackground方法中根据我们设置的查询条件查询数据库,最终会调用到我们的ContentProvider的query方法进行查询查询完成会得到一个Cursor对象,我们调用cursor.setNotificationUri(getContext().getContentResolver(), uri)为这个Cursor设置要监听的Uri,同时CursorLoader会为这个Cursor对象注册一个ForceLoadContentObserver异步任务执行完成后会回调我们的onLoadFinished方法,当我们用ContentProvider增删改数据时,只需在最后调用getContext().getContentResolver().notifyChange(uri, null),就会通知到上面的Cursor对象(因为Uri相同),再由Cursor触发内容观察者的onChange方法,最终又会调用到onForceLoad,重复上述过程,实现数据库变化通知界面。

Android探坑集锦<一>数据库之greendao_第5张图片
查询的时候为这个Cursor设置要监听的Uri
Android探坑集锦<一>数据库之greendao_第6张图片
在update、delete、insert的时候发送通知

到这里我们复习大概总结下sqlite的优势

1.速度快

2.对于大对象而言,listview有天生的支持(CursorAdapter)

3.速度快

但是我们不得不说的是原生cursor不够形象,所以我们接下来看看greendao。

三、什么是greendao

greenDao是一个将对象映射到SQLite数据库中的轻量且快速的开源的ORM解决方案。

官方的介绍

1..最大的性能,可能是android中最快ORM Database解决方案

2.易使用,只需要定义data model,GreenDao会构建data objects(Entities)和

DAOS(data access objects)

3.最少的内存开销

4.依赖的库很小,< 100kb

5.支持数据加密

6.强大的社区

Android探坑集锦<一>数据库之greendao_第7张图片
单位时间内的操作数量

关于greendao和sqlite的速率对比,我这里做了个简单的测试,仅供参考。

Android探坑集锦<一>数据库之greendao_第8张图片
总数据有10000条,一条记录有20+个字段,查询和更新都是针对于这其中的5000条数据的,后面的时间单位为毫秒

我们可以看到在查询和更新的时候还是差的挺多的,因为更新的时候greendao需要先查询出来然后再去更新,查询的话因为要把原生的cursor对象转换成java bean对象。(但是我们项目中一般很少一次性用到这么大数据的查询和更新,同时我们在查询出cursor的使用的时候也需要遍历cursor然后获取对应的字段,才能去使用。我们只是将greean查询转换成对象的这个过程在使用原生sqlite的时候滞后了,放在使用的时候去转换了)

四、greendao怎么实现sqlite上面总结的优势

1.速度快

相较于其他的ORM,greendao是最快的方案了。同时与sqlite相比,insert和delete基本上不相上下,查询和更新在实际使用中,加上原生的sqlite cursor对象遍历和获取字段的时间,总时间应该也是差不多的,同时greendao是有缓存机制,同样的查询条件在后面的查询中会非常快。

2.大对象的支持

针对于cursor大对象,greendao也提供了lazylist,这个lazylist其实就是一个list持有了一个cursor,只有在你使用lazylist的某个对象时它才会进行将cursor某个position进行转换并且加载到内存中。这样既避免了第一次加载大对象消耗过多时间,也会在后续的使用中避免二次加载消耗时间。

3.数据更新通知界面机制

我们可以仿照原生数据库更新界面的机制。在需要时时更新数据库的界面,注册观察者,在数据库相应的表进行更新、删除、新增操作的时候发送给这些观察者,这些观察者接收到消息后只要去重新加载并且刷新界面就好了。(我们项目中因为原有使用了eventbus,所以我们采用了eventbus去处理消息,感兴趣的同学可以参考官网,eventbus和greendao是同一个团队开发的框架)

4.sqlite所不具有的直观形象

它所返回的都是javabean,所有对数据库的操作,我们只需要对于jeanbean进行操作。

五、How使用greendao

Android探坑集锦<一>数据库之greendao_第9张图片
gradle配置


Android探坑集锦<一>数据库之greendao_第10张图片
greendao四大重要的类
Android探坑集锦<一>数据库之greendao_第11张图片
notedao里面就封装了很多针对于note表数据操作的方法

举个栗子

Android探坑集锦<一>数据库之greendao_第12张图片
note表的对应javabean
Android探坑集锦<一>数据库之greendao_第13张图片
获取到对应表dao后操作对象就可以操作i数据库

我们只要将javabean定义好,然后直接buid,系统会帮我们自动生成DaoMaster、DaoSession、Daos。我们只要获取到相应的Dao去操作就好了。greendao操作流程什么的,这里就不啰嗦了,表的关系、join什么的官方文档都很详细,接下来我主要说说我集成过程中的问题。

六、项目集成

Android探坑集锦<一>数据库之greendao_第14张图片
集成后的结构

Datahelper负责与数据库连接、数据库的升级和创建。DatabaseManagerUtil对greendao的操作做了一层封装,它持有一个DaoMasterDaoSession,这样就可以获取到不同的Daos,因为实现数据库更新界面的机制,我也在DatabaseManagerUtil里也是用了UriMatcher,定义了很多不同的Uri,在插入、更新、删除操作的时候会发送不同的uri作为消息,需要时时更新的界面只要注册对于某种uri的监听,收到消息后重新去查询更新界面就好了。这个不同的Table分成不同的Util,这些Util只要去调用DatabaseManagerUtil去执行操作,DatabaseManagerUti底层封装了增、删、改(查询的方法条件过多where、join、or、and)的方法这样的话,即使后续需要更换不同的数据库框架,只需要修改DatabaseManagerUtil封装的那些操作以及部分TableUtil里面查询的就好了。

关于数据库的升级与创建

Android探坑集锦<一>数据库之greendao_第15张图片
greendao自动生成的helper
Android探坑集锦<一>数据库之greendao_第16张图片
greendao自动生成的helper

我们可以看到,greendao生成的helper升级的时候会删掉原来的表然后重新创建,这个明显不符合工程的要求,因此我们需要重写onUpgrade这个方法。

关于数据库多线程存在的同步问题

对于insert,update,delete等会改变数据库的操作,GreenDao底层都是进行了同步(增加了同步锁)

greenDao多线程同步可以通过forCurrentThread()来实现的。为完全地避免可能产生的死锁引入了forCurrentThread()方法。该方法将返回本线程内的Query实例,每次调用该方法时,参数均会被重置为最初创建时的一样。

Android探坑集锦<一>数据库之greendao_第17张图片
greenDao是通过将线程id与query对象存储在Map集合中建立1:N的映射关系,不同线程只会取出属于自己的query而不会调用其他线程的query。

关于数据库事务

Greendao所有的批量操作都增加了事务处理,保证了数据的一致性。同时对于不同的操作也提供了runInTx和callInTx方法,保证所有里面的操作都是在一个事务中。runInTx是异步的、callInTx是同步的。

七、集成总结tips

1.Greendao都是针对于对象的,因此一些联表返回多张表字段的操作无法完成。(当然我们可以仿照它的实现去自己实现cursor加载成对象的部分)

2.Greendao无法创建视图,无法创建触发器。

3.Greendao更新操作的时候必须要先查出来才能去更新,它是根据entity的primary key来进行操作的,这就导致了它的更新速度相较于sqlite差很多。

4.GreenDao目前只支持 只有单个Primary key的表,且为Long类型。多个primary key时可以插入、查询、和删除,但是无法直接更新。

5.对于一些只需要查询出数量的语句,建议用buildCount()构造,这样效率更高

6.Greendao和原生的sqlite可以并存,他们都是读取的db文件进行加载的

7.尽可能多的使用批量操作,这样速度更快

8.Greendao会有sessions缓存,两次相同的操作,会返回相同的对象。第二次只是去读了缓存并没有直接查询。因此在插入、删除、更新某张表时可以清除掉相应表的缓存(调用dao中的detachAll()方法)

9.GreenDao支持懒加载模式lazylist,但是最后必须要手动close

10..对于数据库中是null的字段,Cursor查询,getInt,返回的是0,getLong也是返回0;对于是字符串“acbd”这类的使用Cusor getInt 或者  getLong返回的是0,也就是说并不会抛出异常,但是使用GreenDao是查询到的就是null,因此对于返回integer、long之类的字段使用GreenDao要注意空指针异常(可以修改get方法,如果发现对象是null,就构造一个0返回,这样就避免了在使用的时候需要繁琐的非空判断),另外在进行类型转换String转Int或者Long时,要注意NumberFormatException

11.Greendao在insert的时候如果那条数据有唯一索引,但在数据库中已经存在一条了,就会抛出异常。但是在sqlite中不会崩溃,只是会在返回的id小于0

12.建议在所有的greendao查询语句后面增加forCurrentThread来避免同步问题

13.greendao支持传入sql语句进行查询,但是在使用queryraw传入sqlwhere条件时需要前面增加“WHERE”

还可以使用new WhereCondition.StringCondition(where)),这里就不需要增加“where”

14.在集成过程中发现的sqlite批量操作中的小技巧

重点关注withValueBackReference()这个方法,意思就是批量操作的第一条是插入,会返回插入的id。withValueBackReference(CardContacts.CardRelation.CONTACT_ID, 0)其中的 0表示就是会将批量操作中的第一条操作的返回,将这个值会赋值给CardContacts.CardRelation.CONTACT_ID这个字段,然后进行操作。

其实在集成过程中还遗留了一个问题,在签名打包混淆过程中参照官网文档配置,启动还是会crash,这个问题还在尝试解决中,后续有解决会及时补充。(配置和报错贴出来,)

# greendao 3.2.0混淆

-keepclassmembersclass * extends org.greenrobot.greendao.AbstractDao {

public static java.lang.String TABLENAME;

}

-keep class **$Properties

# If you do not use SQLCipher:

-dontwarnorg.greenrobot.greendao.database.**

# If you do not use Rx:

-dontwarn rx.**


Caused by: org.greenrobot.greendao.DaoException: Could not init DAOConfig                    at org.greenrobot.greendao.b.a.(DaoConfig.java:94)                                                          at org.greenrobot.greendao.b.a(AbstractDaoMaster.java:44)                                            at com.database.entitys.g.(DaoMaster.java:46)                                                                  at com.database.entitys.g.(DaoMaster.java:41)                                                                 at com.database.manager.a.f.(DatabaseManagerUtil.java:89)

Caused by: java.lang.ArrayIndexOutOfBoundsException: length=5; index=11                       at org.greenrobot.greendao.b.a.a(DaoConfig.java:117)                                                     at org.greenrobot.greendao.b.a.(DaoConfig.java:57)                                                         at org.greenrobot.greendao.b.a(AbstractDaoMaster.java:44)                                             at com.database.entitys.g.(DaoMaster.java:46)                                                                 at com.database.entitys.g.(DaoMaster.java:41)                                                                 at com.database.manager.a.f.(DatabaseManagerUtil.java:89)

(只要不混淆就可以正常运行,混淆后启动就crash,郁闷)

上述的混淆问题,最终终于解决了!!!

其实跟进错误,发现在Daomaster中,注册dao类的时候,因为我们本地的混淆文件中配置了对于没有引用的变量会混淆,导致反射去获取Dao类中的Properties里面的字段少了很多,问题找到了,解决就很简单了。

lass propertiesClass = Class.forName(daoClass.getName() + "$Properties");

Field[] fields = propertiesClass.getDeclaredFields();

只要在混淆文件中,保持这些Property不被混淆就可以了。最终添加greendao的混淆文件在下面贴出来。

### greenDAO 3

-keep class org.greenrobot.greendao.**{*;}

-keep public class * extends org.greenrobot.greendao.AbstractDao

-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {

    public static java.lang.String TABLENAME;

}

-keep class **$Properties

-keepclassmembers class **$Properties {*;}

大功告成!!!

End!再次把官网链接贴给大家。

(拖了好久终于完成了第一篇,还是鼓励下自己吧,不喜勿喷)

你可能感兴趣的:(Android探坑集锦<一>数据库之greendao)