Android技术前沿:DBFlow的实践

DBFlow是一个基于AnnotationProcessing(注解处理器)的强大、健壮同时又简单的ORM框架。相比于使用模板代码生成的GreenDao,DBFlow使用更加方便;相比使用反射的ActiveAndroid,在性能有着绝对的优势。
此框架设计为了速度、性能和可用性。消除了大量死板的数据库代码,取而代之的是强大而又简洁的API接口。

相关链接:https://github.com/Raizlabs/DBFlow
官方文档:https://agrosner.gitbooks.io/dbflow/content/

数据持久化主要解决三个问题:

  1. 数据库、表的创建
  2. 表数据的增删改查操作
  3. 数据库的版本管理:版本升级、数据迁移等

本文档将按照这三个问题论述DBFlow的使用

数据库、表的创建

创建Java类指定数据库的名称和版本号。
在Application的OnCreate方法中执行DBFlow SDK方法FlowManager.init(context); SDK会自动创建数据库。

我这里写了一个示例,仅供参考:


@Database(name = AppDatabase.NAME, version = AppDatabase.VERSION)
public final class AppDatabase {
    private AppDatabase() {
    }

    public static final String NAME = "studio"; // 数据库名称

    public static final int VERSION = 2; // 数据库版本号

    public static void setup(Context context) {
        FlowManager.init(context);
    }

    /**
     * 数据库的修改:
     * 1、PatientSession 表结构的变化
     * 2、增加表字段,考虑到版本兼容性,老版本不建议删除字段
     *
     */
    @Migration(version = 2, database = AppDatabase.class)
    public static class Migration2PS extends AlterTableMigration<PatientSession> {

        public Migration2PS(Class<PatientSession> table) {
            super(table);
        }

        @Override
        public void onPreMigrate() {
            addColumn(SQLiteType.TEXT, "patientId");
            addColumn(SQLiteType.TEXT, "pinyin");
            addColumn(SQLiteType.TEXT, "sortLetters");
            addColumn(SQLiteType.INTEGER, "attention");
        }
    }
}

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        AppDatabase.setup(this);
    }
}

表的创建也是通过Java类和Annotation来实现,示例如下:

@Table(database = AppDatabase.class)
public class PatientSession extends BaseModel implements Parcelable {
    @PrimaryKey
    @Column
    public String patientDocId; // 患者档案ID
    @PrimaryKey
    @Column
    public String docId;
    @Column
    public String patientDocName; // 患者档案姓名
    @Column
    public String patientName; // 患者账号微信昵称
    @Column
    public String patientId; // 患者账号微信ID
    @Column
    @SerializedName("note")
    public String noteName; // 备注姓名
    @Column
    public String pinyin;
    @Column
    public String sortLetters;
    ...
}

所有表对象集成BaseModel,该方法实现了该表对应的增删改查操作,数据库表的操作就直接转换成对Java表对象的操作了,简介明了。关于使用到的基本Annotation,@Table、@PrimaryKey、@Column、@Unique、@ForeignKey等,从名字就可以知晓这是表结构的基本术语。表会在使用的时候进行创建,而不是数据库创建的时候全部创建好。

更多Annotation查阅:
https://github.com/agrosner/DBFlowDocs/blob/master/Models.md

表的增删改查、外键关联

基本的增删改查操作通过BaseModel中的save()、delete()、insert()、update()方法来实现。

手动查询操作:

SQLite.select(EmployeeModel_Table.department,
Method.avg(EmployeeModel_Table.salary.as("average_salary")),
                EmployeeModel_Table.title)
      .from(EmployeeModel.class)
      .groupBy(EmployeeModel_Table.department, EmployeeModel_Table.title);
      
  
SQLite.select().from(UserProfile.class).where(UserProfile_Table.id.eq(doctorId)).querySingle();

核心类即 SQLite

事务操作可以通过TransactionManager 提供的相关方法来实现。

TransactionManager.getInstance().addTransaction(new BaseTransaction() {
                @Override
                public Object onExecute() {
                    Logger.d("Save Sessions in a Transaction");
                    for (PatientSession session : sessions) {
                        session.save();
                    }
                    return null;
                }

                @Override
                public int compareTo(Object another) {
                    return 0;
                }
            });

外键关联

官方文档说明:
https://github.com/agrosner/DBFlowDocs/blob/master/Relationships.md

DBFlow提供了外键关联的操作,同大部分ORM框架提供的功能类似,外键关联可实现查询、插入、删除等关联操作。DbFlow当前支持one-one, @OneToMany 和 @ManyToMany三种,one-one好说,后面两个使用时有点坑,简要说明一下。

假设Solution是方案,SolutionItem是方案下面的子项,Solution-SolutionItem构成one-to-many关系,它们的外键关系建立如下:

  1. Solution声明one-to-many 关系
@ModelContainer
@Table(database = AppDatabase.class)
public class Solution extends BaseModel implements Parcelable {
    @PrimaryKey
    public String solutionCode; // 方案编号
    public List<SolutionItem> solutionItems;

    @OneToMany(methods = {OneToMany.Method.SAVE, OneToMany.Method.DELETE},
            variableName = "solutionItems")
    public List<SolutionItem> getSolutionItems() {
        if (solutionItems == null) {
            solutionItems = new Select()
                    .from(SolutionItem.class)
                    .where(SolutionItem_Table.solution_id.eq(solutionCode))
                    .queryList();
        }
        return solutionItems;
    }
}

要点:@ModelContainer @OneToMany 并且指定methods = {OneToMany.Method.SAVE, OneToMany.Method.DELETE}, 或者指定OneToMany.Method.ALL, 该指定可以实现修改的同步。
  1. SolutionItem指定外键
@Table(database = AppDatabase.class)
public class SolutionItem extends BaseModel implements Parcelable, Cloneable {
    @PrimaryKey
    public Integer itemId;
    @Column
    public String itemName;
    
    @ForeignKey(references = {@ForeignKeyReference(columnName = SOLUTION_ID,
            columnType = String.class, foreignKeyColumnName = "solutionCode")},
            saveForeignKeyModel = false)
    ForeignKeyContainer<Solution> solutionModelContainer;

    public void associateSolution(Solution solution) {
        solutionModelContainer = new ForeignKeyContainer<>(Solution.class);
        solutionModelContainer.setModel(solution);
        // put foreignKey
        solutionModelContainer.put("solutionCode", solution.solutionCode);
    }
}

要点:
外键声明:@ForeignKey
外键关联: void associateSolution(Solution solution);
  1. 使用方式,通过调用associateSolution方法建立关联关系,然后再做DB操作,我写了一个测试类,代码如下:
public class SolutionTest {

    @org.junit.Before
    public void setUp() throws Exception {
        Solution solution = new Solution();
        solution.solutionCode = "a";
        List<SolutionItem> solutionItems = new ArrayList<>();
        SolutionItem item1 = new SolutionItem(1, "麻黄");
        SolutionItem item2 = new SolutionItem(2, "三七");
        solutionItems.add(item1);
        solutionItems.add(item2);
        item1.associateSolution(solution);
        item2.associateSolution(solution);
        solution.solutionItems = solutionItems;
        solution.save();
    }

    @org.junit.After
    public void tearDown() throws Exception {

    }

    @Test
    public void testOneToMany() throws Exception {
        List<SolutionItem> items = SQLite.select().from(SolutionItem.class).queryList();
        Assert.assertEquals("one-to-many save success", 2, items.size());

        Solution solution = SQLite.select().from(Solution.class).where(Solution_Table.solutionCode.eq("a")).querySingle();
        Assert.assertEquals("one-to-many get success", 2, solution.getSolutionItems().size());

        solution.delete();

        List<SolutionItem> items2 = SQLite.select().from(SolutionItem.class).queryList();
        Assert.assertEquals("one-to-many delete success", 0, items2.size());
    }
}

数据库的版本管理:版本升级、数据迁移

官方文档说明:
https://github.com/agrosner/DBFlowDocs/blob/master/Migrations.md

https://github.com/agrosner/DBFlowDocs/blob/master/Migration3Guide.md

DBFlow提供了三个Migration方法:

  1. AlterTableMigration
  2. IndexMigration/IndexPropertyMigration
  3. UpdateTableMigration

在前面所述的数据库类定义中有一个TableMigration的示例,实现了表字段的增加,只要在在数据库类中声明Migration类,并override 上述三个Migration基类的方法,在重载方法中做数据库升级等操作。

结语

以上描述大都从实践的角度来说明,有兴趣的读者可以研读一下DBFlow的代码,特别的,现在Annotation的使用是一个非常好的编码方式,可以学习之。另外需要说明的是,以上的实践也是我基于DBFlow官方文档进行的,读者在实践的时候也要结合官方文档,以免信息传递中出现信息差。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容