前段时间工作中接触到了数据库greendao,将项目中所有原生sqlite替换成为了greendao数据库封装框架,对于一些经验和坑进行总结和记录。
一、android原生sqlite
我们一般用Android sqlite无非两种。
1.使用原生的sqlitehelper
简单的使用SQLiteOpenHelper与数据库建立连接,获取到SQLiteDatabase对象,然后就可以进行增删改查操作
2.对于helper做一层封装,使用自定义contentProvider进行查询,查询的时候传入uri,使用UriMatcher根据不同的uri匹配到不同的uri code
(对于UriMatcher的匹配规则,在这里我们啰嗦一下)
二、原生sqlite数据变化通知界面
1.注册数据库的监听
2.使用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,重复上述过程,实现数据库变化通知界面。
到这里我们复习大概总结下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.强大的社区
关于greendao和sqlite的速率对比,我这里做了个简单的测试,仅供参考。
我们可以看到在查询和更新的时候还是差的挺多的,因为更新的时候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
举个栗子
我们只要将javabean定义好,然后直接buid,系统会帮我们自动生成DaoMaster、DaoSession、Daos。我们只要获取到相应的Dao去操作就好了。greendao操作流程什么的,这里就不啰嗦了,表的关系、join什么的官方文档都很详细,接下来我主要说说我集成过程中的问题。
六、项目集成
Datahelper负责与数据库连接、数据库的升级和创建。DatabaseManagerUtil对greendao的操作做了一层封装,它持有一个DaoMasterDaoSession,这样就可以获取到不同的Daos,因为实现数据库更新界面的机制,我也在DatabaseManagerUtil里也是用了UriMatcher,定义了很多不同的Uri,在插入、更新、删除操作的时候会发送不同的uri作为消息,需要时时更新的界面只要注册对于某种uri的监听,收到消息后重新去查询更新界面就好了。这个不同的Table分成不同的Util,这些Util只要去调用DatabaseManagerUtil去执行操作,DatabaseManagerUti底层封装了增、删、改(查询的方法条件过多where、join、or、and)的方法这样的话,即使后续需要更换不同的数据库框架,只需要修改DatabaseManagerUtil封装的那些操作以及部分TableUtil里面查询的就好了。
关于数据库的升级与创建
我们可以看到,greendao生成的helper升级的时候会删掉原来的表然后重新创建,这个明显不符合工程的要求,因此我们需要重写onUpgrade这个方法。
关于数据库多线程存在的同步问题
对于insert,update,delete等会改变数据库的操作,GreenDao底层都是进行了同步(增加了同步锁)
greenDao多线程同步可以通过forCurrentThread()来实现的。为完全地避免可能产生的死锁引入了forCurrentThread()方法。该方法将返回本线程内的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 {*;}