Android数据库框架:greenDAO vs LiteOrm

LeoMeldrum 7年前
   <p>本文主要对比基于 Android SQLiteDatabase 引擎实现的数据库框架:</p>    <p>greenDAO,官网链接 <a href="/misc/goto?guid=4959749215443415805" rel="nofollow,noindex">http://greenrobot.org/greendao</a> 。</p>    <p>LiteOrm,官网链接 <a href="/misc/goto?guid=4959749215530579126" rel="nofollow,noindex">http://litesuits.com</a> 。</p>    <p>写本文机缘起于微信群里有人谈到Android数据库框架,随之一个有赞的朋友 @段扬扬 给了一份自己的测试数据,大概是这样:</p>    <p><img src="https://simg.open-open.com/show/b9014f659db75b6fc6296b76635cc417.jpg"></p>    <p>Android 数据库框架性能测试</p>    <p>由这个图可以看到 Realm( <a href="/misc/goto?guid=4959749215624367045" rel="nofollow,noindex">https://realm.io/cn</a> ) 基本上是性能最好的,它确实是一个牛逼的项目,它不是基于 SQLite 而是基于一个自己的持久化引擎,具有 DB 文件跨平台共享的优点,也存在一些不大不小的问题, <a href="/misc/goto?guid=4958973331819391051" rel="nofollow,noindex">点击这里查看</a> (若链接失效请google关键词“为什么我不再使用Realm”),综合而言,还是可以一战,有兴趣可以自己尝试下。</p>    <p>里面没有 LiteOrm(和 Ormlite 不是一回事)的测试数据,大概是知名度小,我就随后要了一份他的测试代码和原始数据(特别感谢这位同学),不过这份数据不全了,而且需要同一部测试机,所以暂没法做框架间全量对比了。</p>    <p>所以,本文主要针对 greenDAO 和 LiteOrm,因为据说 greenDAO 是基于Android SQLite的最快、性能最强悍的数据库框架,因为他不涉及反射,靠的是代码辅助生成。</p>    <p>那么,我们从几个简单常见的角度和案例出发看看两者的表现如何,将会涉及到:</p>    <ol>     <li>性能表现情况</li>     <li>初步使用情况</li>     <li>应对需求变化</li>     <li>待续...(精力有限,等有机缘在弄噢)</li>    </ol>    <h3>一. LiteOrm 和 greenDAO 的性能表现</h3>    <p>下面是一组直观的测试数据,分为循环操作和批量操作两种场景:</p>    <p><img src="https://simg.open-open.com/show/67925c6da799a1a323ea4fbbd209ebd3.png"></p>    <p>greenDAO vs LiteOrm 循环测试</p>    <p><img src="https://simg.open-open.com/show/07e16a111a9f398c314f3a6120b44b0f.png"></p>    <p>greenDAO vs LiteOrm 批量测试</p>    <p>测试相关过程:</p>    <ol>     <li> <p>运行 Test Demo,点击 LiteOrm 测试按钮,通过日志观察执行完毕。</p> </li>     <li> <p>命令行卸载 Test Demo,重新运行,点击 GreenDAO 测试按钮,通过日志观察执行完毕。</p> </li>     <li> <p>每次点击按钮,所有操作会连续测试 10 次,取 10 次消耗时间的均值,安静等待结果就好了。</p> </li>    </ol>    <p>测试相关信息:</p>    <ol>     <li> <p>测试机为 Nexus5,取 10 次消耗时间的均值。</p> </li>     <li> <p>为了更直观清晰的观察数据,将循环操作和批量操作分开统计,否则因为两者数据差异过大,柱状图无法看清小数据的数值。</p> </li>     <li> <p>循环单个操作比较耗时,每次操作 1000 条数据。</p> </li>     <li> <p>批量操作因为整体是事务的,效率非常高,每次操作 100000 条数据。</p> </li>    </ol>    <p>测试相关结论:</p>    <ol>     <li> <p>[循环插入]、[循环更新] 以及 [批量更新] 时,LiteOrm性能略强于greenDAO。</p> </li>     <li> <p>[批量插入]、[查询操作] 时,LiteOrm性能略逊于greenDAO。</p> </li>     <li> <p>除了 [批量查询] 以外,其他性能优劣势差距不明显,[批量查询]耗时差异主要来源于 LiteOrm 采用反射创建实例并赋值属性,而 greenDAO 使用 new 操作直接创建对象并直接赋值属性。</p> </li>    </ol>    <h3>二. LiteOrm 和 greenDAO 的用法对比</h3>    <p>我们以Note对象为例,展示操作过程,Note类如下:</p>    <pre>  <code class="language-java">public class Note {    private Long id;    private String text;    private String comment;    private java.util.Date date;      public Note() {}      public Note(Long id, String text, String comment, java.util.Date date) {          this.id = id;          this.text = text;          this.comment = comment;          this.date = date;      }      // getter and setter...  }</code></pre>    <p>实例化它:</p>    <pre>  <code class="language-java">Note note = new Note(null, "title", "comment", new Date());</code></pre>    <p>1. greenDAO 增改查删</p>    <p>1.1 New Module:即新建一个子项目模块,选择 Java Libray,它是一个java 项目,用来生成 greenDAO 所需要的辅助代码。</p>    <p>1.2 写DAO生成器:即子模块里写一个 DAOGenerator 类生成 DAO、Master、Session 等对象:</p>    <pre>  <code class="language-java">public class NoteDaoGenerator {        public static void main(String[] args) throws Exception {          Schema schema = new Schema(1, "lite.dbtest.greendao");            addNote(schema);            new DaoGenerator().generateAll(schema, "./app/src/main/java");      }        /** 指定 表名 和 列名,以及主键 */      private static void addNote(Schema schema) {          Entity note = schema.addEntity("Note");     // 默认表名为类名          note.setTableName("CustomNote");            // 自定义表名          note.addIdProperty().primaryKey().autoincrement(); //设置自增的主键          note.addStringProperty("text").notNull(); // 非空字段          note.addStringProperty("comment");          note.addDateProperty("date");      }  }</code></pre>    <p>1.3 开始工作:实例化 DAO 对象后执行各种操作:</p>    <pre>  <code class="language-java">// 实例化 DAO  DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "greendao-notes", null);  SQLiteDatabase db = helper.getWritableDatabase();  DaoMaster daoMaster = new DaoMaster(db);  DaoSession daoSession = daoMaster.newSession();  NoteDao noteDao = daoSession.getNoteDao();    // 执行插入  noteDao.insert(note);  // 执行更新  noteDao.update(note);  // 执行查询  noteDao.queryBuilder().where(NoteDao.Properties.Id.eq(1)).list();  // 执行删除  noteDao.delete(note);</code></pre>    <p>2. LiteOrm 增改查删</p>    <p>2.1 开始工作:实例化 LiteOrm 对象后执行插入操作:</p>    <pre>  <code class="language-java">// 实例化 LiteOrm  LiteOrm liteOrm = LiteOrm.newSingleInstance(this, "liteorm-notes");    // 执行插入  liteOrm.insert(note);  // 执行更新  liteOrm.update(note);  // 执行查询  liteOrm.queryById(1, Note.class);  // 执行删除  liteOrm.delete(note);</code></pre>    <p>2.2 不要沉迷,没有第二步,操作已经完了。。。</p>    <p>简单解释下:上面例子默认类名为表名,字段名为列名,id(或者_id)属性为主键,但若要自定义 表名、列名 和 主键,需要给Model加注解:</p>    <pre>  <code class="language-java">// table name is "lite-note"  @Table("lite-note")  public class Note {        @Column("_id")      @PrimaryKey(AssignType.AUTO_INCREMENT)      private Long id; // column name is "_id"        @NotNull      @Column("_text")      private String text;// column name is "_text"        private String comment;// column name is "comment"      private java.util.Date date;// column name is "date"  }</code></pre>    <h3>二. LiteOrm 和 greenDAO 面对需求或模型更改</h3>    <p>举个简单例子,Note对象增加了一系列新字段,假设新增一个[title] 的属性:</p>    <pre>  <code class="language-java">public class Note {      private Long id;      private String title; // 新增这个 title 字段      private String text;      private String comment;      private java.util.Date date;        // 其他省略  }</code></pre>    <p>greenDAO 面对需求or对象模型更改</p>    <p>第1步自定义 SQLiteOpenHelper</p>    <p>greenDAO 常见得做法是在自定义你使用的 SQLiteOpenHelper,比如下面方案。</p>    <p>方案a : 删旧表,建新表。</p>    <pre>  <code class="language-java">/** WARNING: Drops all table on Upgrade! Use only during development. */      public static class DevOpenHelper extends OpenHelper {          public DevOpenHelper(Context context, String name, CursorFactory factory) {              super(context, name, factory);          }            @Override          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {              // 方案1,删旧表,建新表。              dropAllTables(db, true);              onCreate(db);          }      }</code></pre>    <p>方案b :为旧表添加新列。</p>    <pre>  <code class="language-java">/** WARNING: Drops all table on Upgrade! Use only during development. */      public static class DevOpenHelper extends OpenHelper {          public DevOpenHelper(Context context, String name, CursorFactory factory) {              super(context, name, factory);          }            @Override          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {              // 方案2,为旧表添加新列。              db.execSQL("ALTER TABLE NOTE ADD COLUMN title");          }      }</code></pre>    <p>如果不做上面操作,那么升级后,是会发生下面这个异常,因为之前建的表不存在[title]这个列呀!</p>    <pre>  <code class="language-java">android.database.sqlite.SQLiteException: no such table: CustomNote (code 1): , while compiling: INSERT INTO "CustomNote" ("_id","TEXT","COMMENT","TITLE","DATE") VALUES (?,?,?,?,?)</code></pre>    <p>第2步修改 DAOGenerator 重新生成代码</p>    <p>因为 greenDAO 操作的模型是由其代码生成工具产生的,需要在 DAOGenerator 里添加一个字段,让其重新生成一次模型。</p>    <pre>  <code class="language-java">Schema schema = new Schema(2, "lite.dbtest.greendao"); // 升级数据库版本    Entity note = schema.addEntity("Note");   note.setTableName("CustomNote");  note.addIdProperty().primaryKey().autoincrement();  note.addStringProperty("title"); // 这里新增一个字段  note.addStringProperty("text").notNull();  note.addStringProperty("comment");  note.addDateProperty("date");     new DaoGenerator().generateAll(schema, "./app/src/main/java");</code></pre>    <p>运行,greenDAO 通过 Schema 设置了数据库版本,为我们生成了系列新的Note、Note DAO、Master、Session等类。</p>    <p>至此,然后基本完成 [添加一个属性字段] 的升级改造。</p>    <p>LiteOrm 面对需求or对象模型更改</p>    <p>好害怕,会不会更麻烦。。。but。。。</p>    <p>事实是 1 步也不需要走,什么都不用改,因为模型里面我们已经新增了一个字段:</p>    <pre>  <code class="language-java">public class Note {      private Long id;      private String title; // 新增这个 title 字段      // ... 其他省略</code></pre>    <p>这已经足够了,这受益于 LiteOrm 的自动探测技术,它会智能的判断某个对象是不是发生了改变,从而同步到数据库,这一切,开发者是无感知的。</p>    <p>限于时间和个人精力问题,这篇分析并不全面,如果有误还请不吝指正。不论哪款 ORM 或 数据库框架,都各有利弊,至于该选用哪一款,可自行斟酌,开发者最好自己亲身体验下,毕竟绝知此事需躬行,只听或者看别人的言论和结果,无异于直接吃别人嚼过的东西,没有味道不重要,变了味会影响个人判断。</p>    <p>测试代码已经上传Github:</p>    <p><a href="/misc/goto?guid=4959749215744123530" rel="nofollow,noindex">https://github.com/litesuits/for-test/tree/master/DataBaseTest</a></p>    <p>最后,附一份<LiteOrm 和 系统原生SQLiteDatabase API 测试数据></p>    <p><img src="https://simg.open-open.com/show/7a0ad49e6ae238275d9f359f4446528f.png"></p>    <p>lite-vs-system.png</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/330bbd3b0e68</p>    <p> </p>