Android 数据库操作框架对比分析

对比.png
1.png
2.png
3.png
4.png
5.png
6.png
7.png
8(ObjectBox10万-100万未测试).png
1. 源生的SQLiteDatabase
  • DB操作执行方式一(官方推荐):

优点:直接调用官方api,无需手写sql语句

缺点:执行效率低,查询需要手写解析代码

图1 - 源生数据库执行方式一.png
  • DB操作执行方式二(使用较少):

优点:执行效率比方式一要高

缺点:需要手写sql语句,查询需要手写解析代码,无校验,没有对多线程做处理

图2 - 源生数据库执行方式二.png
2. Room
  • annotationProcessor生成java原理,过程如图3所示。
图3 - 自定义注解生成java文件原理.png
  1. 编译期通过kapt(Kotlin编写)扫描出注解标记,然后根据注解处理器中的逻辑运用javapoet动态代码生成工具生成对应的数据库初始化及Dao层的java文件。

  2. 生成的Dao层java文件中,将注解参数转换为对应的sql语句。

  3. 增/删/改注解:

  • 生成固定的sql格式,通过占位符方式预留参数位置,如图4所示
图4 - 生成固定占位符的sql语句.png
  • 获取SQLiteStatement对象直接执行sql语句,在SQLiteStatement的缓存对象可用时,复用缓存的SQLiteStatement对象,否则新建对象。利用原子性布尔值AtomicBoolean控制多线程,一个线程对应一个SQLiteStatement对象,保证每个事务执行完毕再释放SQLiteStatement,如图5所示。
图5 - 增删改操作的sqlstatement生成原理.png
  1. 查询注解:
  • 与增/删/改注解不同,查询注解标记的每个方法都生成一一对应的sql语句。

  • 尝试从TreeMap中获取对应的RoomSQLiteQuery对象,如果没有缓存则新建,并缓存到TreeMap中(key为参数个数,value为RoomSQLiteQuery)。

  • 最终调用SQLiteDatabase的rawQueryWithFactory方法传入RoomSQLiteQuery对象中的sql查询语句,获取结果列表的查询游标,先获取每个查询参数的对应所在列,再通过游标循环遍历出所有的数据,如图6所示。

图6 - 查询操作的逻辑.png

结论:
优点:
1)通过注解生成代码,减少代码量;
2)编译期SQL检查机制,降低风险;
3)对源生数据库做了一些优化性能的改写。

缺点:没有缓存机制。

3. GreenDao
  1. 预先通过模板引擎框架FreeMarker将需要生成的模板内置在框架中,在编译期通过JDT注解处理器(不开源)将扫描到的自定义注解解析成对应的参数填充到模板中,生成对相应的java文件。其中自动生成的Dao层文件中的静态内部类Properties是条件查询的关键枢纽(GreenDao提供的查询Api调用queryBuilder使用这些参数生成WhereCondition对象来完成条件查询),也正是因为这些参数是在编译期生成的,无法让ide获悉他们与对象间的关联,所以添加表或者修改表字段的时候需要编译2次。

  2. 运行期通过反射获取编译期生成的Dao对象中的TABLENAME参数及Properties中的静态字段作为新建表中的表名及字段名,并新建所有数据表,如图7所示。

图7 - 反射获取Dao文件的表名及字段.png
  1. 每个Dao对象都包含一个TableStatements对象,TableStatements持有增/删/改/替换/列数查询等5个SQLiteStatement参数。
图8 - TableStatements中的SQLiteStatement.png
  1. 在初始化数据库的时候,可以选择是否采用缓存机制(只有设定主键的表才可以开启)。缓存IdetityScope对象为key,value的缓存结构。

  2. 增/删/改:

获取TableStatments中对应的SQLiteStatement对象,先判断当前线程是否持有数据库的活动连接,有则启动同步锁执行DB操作,否则启动事务执行。最后把数据更新到IdetityScope缓存中(key为主键,value为对应的entity),如图9所示。

图9 - 数据库更新操作.png
  1. 查询:
  • 多线程处理,Query对象绑定了Query对象被创建时的线程,并缓存到Map中,key为线程id,value为Query,如果当前线程已有绑定的Query,则复用。如图10所示。
图10 - 获取Query对象.png
  • 每次查询都会通过Query对象list方法执行DB操作获取到Cursor游标(无论是否有缓存),如图10所示。
图11 - 数据库执行查询操作获取游标.png
  • 然后通过游标获取字段的所在列,判断缓存IdetityScope中是否有对应的实体对象,有则从缓存取出,否则遍历游标的参数拼装实体返回。

结论:
优点:
1)通过注解生成代码,减少代码量;
2)通过api调用完成操作,多数情况下避免编写sql语句
3)缓存机制,提升了一定的效率(但每次都会执行sql操作)

缺点:
1)通过JDT生成的文件无法与ide建立关联,新增或修改相关表结构需要两次构建,特别是项目后期,特别浪费时间;
2)学习成本较高,除了要学习相关的注解使用,还要学习相关的api调用方法

综上分析,单从性能方面看,在循环执行数据库操作时,3个框架的执行效率都在伯仲之间,而在批量操作时,ObjectBox作为NOSQL有很大的优势,完全碾压GreenDao和Room这两个框架。但是ObjectBox在平台支持方面非常局限,目前只支持2个平台。对于2C产品来说,终端设备千差万别,基本不可能保证所有的终端都属于这两个平台。

而从GreenDao和Room这两个框架对比来看,GreenDao通过编译自动生成Dao层,在前期开发中更有优势,但随着项目的迭代和维护,每次进行DB的扩展都需要重新构建项目,无疑会增加调试的时间。而Room刚好相反,Dao层因为需要程序员自行研发,在前期会增加一些开发工作量,但是后期无论如何扩展,它的调试效率始终是如一的。在查询性能方面,查询量在100000条数据时,GreenDao只比Room快了大概800毫秒,而Room是Google的官方开源组件,会得到更好的官方支持,从LiveData就能看出。

你可能感兴趣的:(Android 数据库操作框架对比分析)