*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發(fā)布
為啥要寫數(shù)據(jù)庫呢欠母?公司的項目一直在進行著調(diào)整膘怕,整個項目的 module 已經(jīng)超過 20暇检,為了做到徹底解耦挣柬,我們的組的扛把子可謂是大刀闊斧钥顽,還多次向我表達了我們的 storage 模塊需要調(diào)整的感慨庇忌。先說說現(xiàn)狀舞箍,目前項目中使用的是 Ormlite,總體的感覺就是使用起來還是要寫很多代碼皆疹,看著之前的升級版本的邏輯頭大疏橄。由于構(gòu)建和升級的邏輯都在 storage 模塊,所以要添加和修改表的話就要一定會修改到這個模塊的代碼,說白了還是耦合捎迫。
所以需求就是兩點:
- 使用簡單晃酒,升級方便。
- 多模塊使用窄绒,讓各個 module 負責各自的表贝次。(這是不是就是一個 module 對應一個數(shù)據(jù)庫來著)
上面兩點是我學習了解數(shù)據(jù)庫框架的目的,所以 DBFlow 也只是學習和嘗試的框架之一彰导,其他的后續(xù)再說蛔翅。
簡介
簡單說明一下,本文使用的是 DBFlow 的 4.1.2 版螺戳,也是截止目前為止的最新版本搁宾,在 github 上也可以看到,目前 DBFlow 還提供了一系列拓展倔幼,包括對 kotlin 的支持盖腿,Rxjava 的支持,數(shù)據(jù)庫加密等损同。當然我是帶著目的來的翩腐,所以我還是會主要關注上面提出來的兩點需求,至于一些詳細的使用還是盡力吧膏燃。
配置
為了后面多 Module 使用方便茂卦,先在項目中創(chuàng)建 config.gradle 文件,當然這個不是必須的组哩。
// config.gradle
ext {
compileSdkVersion = 26
buildToolsVersion = "26.0.2"
minSdkVersion = 15
targetSdkVersion = 26
versionCode = 1
versionName = "1.0"
dbflow_version = "4.1.2"
}
對項目的 build.gradle 文件做以下修改:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle" // 沒有創(chuàng)建 config.gradle 就不是必須
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
}
}
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" } // 添加
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
在 app Module 中的 build.gradle 中添加依賴
dependencies {
// ...其他依賴
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:$dbflow_version"
// gradle 3.0.0 可以使用 implementation等龙,否則用 compile
implementation "com.github.Raizlabs.DBFlow:dbflow-core:$dbflow_version"
implementation "com.github.Raizlabs.DBFlow:dbflow:$dbflow_version"
}
到這里已經(jīng)配置完畢了,下面我們開始愉快的創(chuàng)建數(shù)據(jù)庫了伶贰。
創(chuàng)建數(shù)據(jù)庫與表
新建 App 繼承 Application蛛砰,并在其中初始化:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
FlowManager.init(this);// 初始化
}
}
新建數(shù)據(jù)庫
@Database(version = AppDatabase.VERSION)
public class AppDatabase {
public static final int VERSION = 1;
}
新建表
新建 Product 類,并用注解 @Table 標注黍衙,指定它的數(shù)據(jù)庫為 AppDatabase泥畅;用 @PrimaryKey 標注 id 為主鍵,并且為自增長琅翻;用 @Column 標注 name 為表中的一列位仁。
@Table(database = AppDatabase.class)
public class Product extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String name;
}
這里我們的表算是建完了,只需要點擊 AndroidStudio 的 Build -> Make Project 之后方椎,就可以在 app\build\generated\source\apt\debug 目錄下找到生成的類 Product_Table聂抢,在里面可以看到一些 SQL 語句。
注意:這里我們繼承的 BaseModel 是 DBFlow 給我們提供的辩尊,并不是我們自己項目中的涛浙,所以有人可能有疑問:我可不可以不繼承它?答案是可以的,差別僅僅增刪改查的操作上有所不同轿亮。
CRUD
DBFlow 對數(shù)據(jù)的增刪改查已經(jīng)做了封裝疮薇,使用起來比較簡單,也不很容易理解我注。
Insert
對于向數(shù)據(jù)庫插入數(shù)據(jù)的操作按咒,對于已經(jīng)繼承了 BaseModel 的 bean,我們可以直接 new 一個出來但骨,給相應的屬性賦值之后励七,直接調(diào)用 save() 方法,數(shù)據(jù)就保存完畢了奔缠,代碼如下掠抬。
另外,這里我們并沒有給 Product 的主鍵 id 賦值校哎,但是在保存完之后這個 id 就被賦值了两波。
Product product = new Product();
product.name = "P" + (System.currentTimeMillis() % 10000);
product.save();
// 執(zhí)行到這里之后 id 已經(jīng)被賦值
Query
數(shù)據(jù)的查詢,這里舉了一個簡單但是平時使用較為頻繁的例子闷哆。從前文我們可以知道 Product_Table 是 apt 給我們生成的腰奋,使用起來也很靈活易懂,當然其他的更為復雜的建議大家可以查看一下官方文檔抱怔。
List<Product> products = SQLite.select()
.from(Product.class)
.where(Product_Table.name.isNotNull(), Product_Table.id.greaterThanOrEq(5L))// 這里的條件也可以多個
.orderBy(Product_Table.id, true)// 按照 id 升序
.limit(3)// 限制 3 條
.queryList();// 返回的 list 不為 null劣坊,但是可能為 empty
Update
更新和刪除可以為先查詢后操作,只要查到對應的數(shù)據(jù)屈留,在 bean 上做修改局冰,然后調(diào)用 update() 方法,數(shù)據(jù)庫就能修改完成灌危。還有另一中更接近 sql 語法的方式锐想。
// 第一種 先查后改
Product product = SQLite.select()
.from(Product.class)
.querySingle();// 區(qū)別與 queryList()
if (product != null) {
L.d("Update: " + product.name + " update to P0000");
product.name = "P0000";
product.update();
}
// 第二種
SQLite.update(Product.class)
.set(Product_Table.name.eq("PXXXX"))
.where(Product_Table.name.eq("P0000"))
.execute();
Delete
與更新操作類似。
// 第一種 先查后刪
Product product = SQLite.select()
.from(Product.class)
.querySingle();
if (product != null) {
product.delete();
L.d("Delete: " + product.name);
}
// 第二種
SQLite.delete(Product.class)
.where(Product_Table.name.eq("PXXXX"))
.execute();
對于未繼承 BaseModel bean 的 CRUD
對于沒有繼承 BaseModel 的 bean乍狐,我們可以用以下方式進行數(shù)據(jù)操作,事實上前面的 save() 等方法最終也是通過這樣處理的固逗。當然定義表的時候的注解不可或缺浅蚪。
這里,考慮到更新和刪除的第二種方法烫罩,嘗試了一下惜傲,果然還可以寫出類似的插入方法。
Product product = new Product();
product.name = "P" + (System.currentTimeMillis() % 10000);
FlowManager.getModelAdapter(Product.class).insert(product);
// 又一種插入方法
SQLite.insert(Product.class)
.columnValues(Product_Table.name.eq("P" + (System.currentTimeMillis() % 10000)))
.execute();
版本升級
因新建表升級
好了現(xiàn)在我們已經(jīng)清楚了 DBFlow 的基本使用了贝攒,但是一張表不能滿足我們的需求暗撂堋!我還想創(chuàng)建一張 Category 表,那么趕緊再創(chuàng)建個類加下注解吧哈踱。
@Table(database = AppDatabase.class)
public class Category extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String name;
}
簡單歸簡單荒适,但是還是試一下增刪改查功能吧!
Category category = new Category();
category.name = "food";
category.save();
運行开镣,之后就會發(fā)現(xiàn)了崩潰信息:
android.database.sqlite.SQLiteException: no such table: Category (code 1): ,
while compiling: INSERT INTO `Category`(`name`) VALUES (?)
這里我們遺漏了數(shù)據(jù)庫版本的升級刀诬,對于增加表格來說,DBFlow 版本升級其實很簡單邪财,我們只要找到我們的數(shù)據(jù)庫類陕壹,并且把他的版本號加 1。
@Database(version = AppDatabase.VERSION)
public class AppDatabase {
public static final int VERSION = 2;
}
再重新運行下树埠,我們就能在不影響 Product 表的前提下糠馆,成功新建了 Category 表了。
因修改表結(jié)構(gòu)升級
DBFlow 的表結(jié)構(gòu)修改是通過 Migration 進行的怎憋,通過對它的實現(xiàn)又碌,來進行對表的操作。
public interface Migration {
/**
* Called before we migrate data. Instantiate migration data before releasing it in {@link #onPostMigrate()}
* 在修改之前執(zhí)行盛霎。
*/
void onPreMigrate();
/**
* Perform your migrations here
* 執(zhí)行數(shù)據(jù)庫操作
* @param database The database to operate on 我們需要操作的數(shù)據(jù)庫
*/
void migrate(@NonNull DatabaseWrapper database);
/**
* Called after the migration completes. Release migration data here.
* 在修改之后執(zhí)行赠橙,釋放資源
*/
void onPostMigrate();
}
雖然看到這里,還是不知道怎么使用它愤炸,不過不用擔心期揪,DBFlow 已經(jīng)有它的幾個現(xiàn)成的實現(xiàn)提供給我們進行使用。
- AlterTableMigration 用于重命名表规个,增加列
- IndexMigration/IndexPropertyMigration 用于索引創(chuàng)建和刪除
- UpdateTableMigration 升級數(shù)據(jù)庫的時候更新數(shù)據(jù)
下面我們就舉一個相對常見的列子來看一下如何進行表結(jié)構(gòu)的修改凤薛。
首先我們先修改一下之前創(chuàng)建的 Product 表:
@Table(database = AppDatabase.class)
public class Product extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String name;
@Column(defaultValue = "10000")// 設置默認 100 塊錢,即使忘了錄入價格诞仓,我們賣出去也不吃虧(注意該屬性坑爹)
public long price;// 分
@Column
public String manufacturer;
}
這里我給加上了兩列 price 和 manufactuer缤苫,然后希望價格默認為 100 元,接著寫 Migration墅拭。
@Migration(version = 3, database = AppDatabase.class)
public static class Migration3 extends AlterTableMigration<Product>{
public Migration3(Class<Product> table) {
super(table);
}
@Override
public void onPreMigrate() {
addColumn(SQLiteType.INTEGER, "price");
addColumn(SQLiteType.TEXT, "manufacturer");
}
}
- 因為是添加表的列活玲,所以繼承 AlterTableMigration;
- 修改 AppDatabase 的版本為 3谍婉,因為我們之前添加過 Category 表 version 為 2舒憾;
- 添加注解,注解中 version 為現(xiàn)在的版本號 3穗熬,database 為我們的 AppDatabase 表镀迂;
- 重寫 onPreMigrate() 方法添加 addColumn() 就是我們在 Product 中新加的字段;
有疑問唤蔗!如果一次升級我們不止改了一處探遵,還有涉及到其他的修改咋辦窟赏?
看到 @Migration 注解中有個 priority 這里我們姑且多建幾個 Migration 用 priority 區(qū)分優(yōu)先級來試試吧!
@Migration(version = 3, priority = 2, database = AppDatabase.class)
public static class Migration3 extends AlterTableMigration<Product>{
// 省略...
}
// 新建兩個打上 log
@Migration(version = 3, priority = 0, database = AppDatabase.class)
public static class Migration3Zero extends BaseMigration {
@Override
public void onPreMigrate() {
L.d("Migration3Zero onPreMigrate: ");
}
@Override
public void migrate(DatabaseWrapper database) {
L.d("Migration3Zero migrate: ");
}
@Override
public void onPostMigrate() {
L.d("Migration3Zero onPostMigrate: ");
}
}
@Migration(version = 3, priority = 1, database = AppDatabase.class)
public static class Migration3One extends BaseMigration {
// 省略...
}
輸出結(jié)果為:
12-07 15:00:19.406 25213-25213/com.dthfish.dbflowdemo D/DBLog: Migration3Zero onPreMigrate:
12-07 15:00:19.406 25213-25213/com.dthfish.dbflowdemo D/DBLog: Migration3Zero migrate:
12-07 15:00:19.406 25213-25213/com.dthfish.dbflowdemo D/DBLog: Migration3Zero onPostMigrate:
12-07 15:00:19.406 25213-25213/com.dthfish.dbflowdemo D/DBLog: Migration3One onPreMigrate:
12-07 15:00:19.406 25213-25213/com.dthfish.dbflowdemo D/DBLog: Migration3One migrate:
12-07 15:00:19.406 25213-25213/com.dthfish.dbflowdemo D/DBLog: Migration3One onPostMigrate:
可以看到 priority 小的執(zhí)行順序優(yōu)先箱季,如果想要指定兩個Migration 的 priority 相同的同學涯穷,就不要找不痛快了,因為沒法保證執(zhí)行順序规哪。
接下來看一下我們更新的表格內(nèi)容:
- 查詢之前的數(shù)據(jù)求豫,已經(jīng)成功的添加了兩個屬性,但是 price = 0诉稍;
12-07 15:00:19.426 25213-25213/com.dthfish.dbflowdemo D/DBLog:
Query: [
Product{id=6, name='P6396', price=0, manufacturer='null'},
Product{id=7, name='P9297', price=0, manufacturer='null'},
Product{id=8, name='P8988', price=0, manufacturer='null'},
Product{id=9, name='P7232', price=0, manufacturer='null'},
Product{id=10, name='P7147', price=0, manufacturer='null'},
Product{id=11, name='P7634', price=0, manufacturer='null'}
]
- 新插入一條數(shù)據(jù)后蝠嘉,查詢——然并軟,說好的 defaultValue 呢杯巨!你還我的 100 塊蚤告!
12-07 15:06:47.666 25213-25213/com.dthfish.dbflowdemo D/DBLog:
Query: [
Product{id=6, name='P6396', price=0, manufacturer='null'},
Product{id=7, name='P9297', price=0, manufacturer='null'},
Product{id=8, name='P8988', price=0, manufacturer='null'},
Product{id=9, name='P7232', price=0, manufacturer='null'},
Product{id=10, name='P7147', price=0, manufacturer='null'},
Product{id=11, name='P7634', price=0, manufacturer='null'},
Product{id=12, name='P1172', price=0, manufacturer='null'}
]
關于失效的的 defaultValue
好吧關于 defaultValue 失效我是始料未及的,在 github 上的 Issues 中查看了一下服爷,雖然有類似的問題但是還是沒有找到正確的處理方法(希望不是我英文水平的問題)杜恰,如果有同學知道正確的方法請務必聯(lián)系我!以免我誤人子弟仍源!
但是我嘗試出了我自己的方法心褐。我的數(shù)據(jù)庫要升級版本 4 啦!
@Table(database = AppDatabase.class)
public class Product extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String name;
@Column//(defaultValue = "10000")// 設置默認 100 塊錢笼踩,即使忘了錄入價格逗爹,我們賣出去也不吃虧
public long price = 10000L;// 分
@Column
public String manufacturer;
}
@Migration(version = 4, database = AppDatabase.class)
public static class Migration4 extends UpdateTableMigration<Product> {
public Migration4(@NonNull Class<Product> table) {
super(table);
}
@Override
public void onPreMigrate() {
where(Product_Table.price.eq(0L));
set(Product_Table.price.eq(10000L));
}
}
- 去掉了 defaultValue 直接給 price 一個默認值;
- 對版本 3 的補救措施嚎于,寫了 Migration4掘而,在 onPreMigrate 方法中對歷史數(shù)據(jù)進行了處理。
刪掉表格的一列
見 github Issue #467
This is a SQLite question, and short answer is...not easily. You have to (this is with SQLite too):
- Create new table without column with temporary name
- Copy over data to it
- Drop old table
- Recreate new table with column left out
As I said, not easy.
- 創(chuàng)建一個沒有那一列的臨時的表
- 把數(shù)據(jù)復制進去
- 刪掉舊的表
- 重新建個表
令我不禁想起了郭神的 LitePal于购,對刪除列做的良心處理袍睡。
多 module 使用
其實說了這么多,我最關心的還是多 module 的使用肋僧,畢竟我最初的目的還是這個斑胜。接下來的過程有些曲折,我會把過程中出現(xiàn)的錯誤以及處理方法都記錄下來嫌吠。
創(chuàng)建多個 Module 添加依賴
這里除去 app伪窖,我又創(chuàng)建了 base,special居兆,ship 三個 module,下面看一下他們的依賴配置竹伸。
// base build.gradle
dependencies {
// 其他...
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:$dbflow_version"
api "com.github.Raizlabs.DBFlow:dbflow-core:$dbflow_version"
api "com.github.Raizlabs.DBFlow:dbflow:$dbflow_version"
}
// ship build.gradle
dependencies {
// 其他...
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:$dbflow_version"
api project(':base')
}
// special build.gradle
dependencies {
// 其他...
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:$dbflow_version"
api project(':base')
}
// app build.gradle
dependencies {
// 其他...
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:$dbflow_version"
implementation project(':ship')
implementation project(':special')
}
這里不用在意 api 和 implementation泥栖,它們是 com.android.tools.build:gradle:3.0.0 才有的指令簇宽,統(tǒng)統(tǒng)可以改成為 compile。到這里我重新跑了一下程序吧享,發(fā)現(xiàn)通過了魏割,那還等什么趕緊試一下。
在 ship module 中新建數(shù)據(jù)庫和表
我沒有把 AppDataBase 移到 base module 中钢颂,因為我希望各個 module 維護自己的表的時候不要修改到底層的 module钞它,所以在 ship 中創(chuàng)建自己的 ShipDataBase。
@Database(version = ShipDatabase.VERSION)
public class ShipDatabase {
public static final int VERSION = 1;
}
@Table(database = ShipDatabase.class)
public class ShipProduct extends BaseModel{
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String name;
}
開始痛苦的解決問題
和前文一樣殊鞭,添加插入和查詢方法遭垛,build 報錯:
Error:Error converting bytecode to dex:
Cause: com.android.dex.DexException: Multiple dex files define Lcom/raizlabs/android/dbflow/config/GeneratedDatabaseHolder;
What a f**k! 好吧強大的 google 指引我到 github Issue #266。
里面信息一堆操灿,但是我隱約找到了我要的答案:
What I learned is you can pass arguments to the processor. For example, the following addition to your build.gradle file >will pass the target module name to DBFlow-Compiler:
apt { arguments { targetModuleName 'DBFlow' } }
This will result in the database holder being named DBFlowGeneratedDatabaseHolder. It will be initialized as discussed >in previous comments.
另外還有人提出 apt 已經(jīng)不維護了:
I use
apt { arguments { targetModuleName 'DBFlow' } }
this solution in android studio 2.3.3
and that solves my problem but today I update my android studio to version 3.0 and we don't have apt or we cannot use it so any solution for this version of the android studio
I also ask the question in StackOverflow https://stackoverflow.com/questions/46998943/how-to-set-prefix-to-generateddatabaseholder-java-class-in-dbflow-in-android-stu
就是說锯仪,在使用 apt(咋配置就不提了,大家可以查一下) 的情況下趾盐,可以在 Ship 的 module 的 build.gradle 中添加:
apply plugin: 'com.android.library'
android {
// 省略...
}
apt {
arguments{
targetModuleName 'Ship'
}
}
dependencies {
// 省略...
}
那么就可以解決問題庶喜,通過 build 的話,最終生成的類名會是 ShipGeneratedDatabaseHolder救鲤,與 app module 中的GeneratedDatabaseHolder 區(qū)別久窟。
但是現(xiàn)在的問題是 apt 已經(jīng)不維護了,我們是否還能通過啥方法進行處理呢本缠?
Android注解使用之注解編譯android-apt如何切換到annotationProcessor
按照上文斥扛,我又修改了 Ship 的 build.gradle 文件:
android {
// 省略...
defaultConfig {
// 省略...
jackOptions {
enabled true
}
javaCompileOptions {
annotationProcessorOptions {
arguments = [ targetModuleName : 'Ship' ]
}
}
}
// 省略...
}
編譯,報錯:
好吧再改搓茬!AndroidStudio 告訴了我們解決方案:
android {
// 省略...
defaultConfig {
// 省略...
android.compileOptions.sourceCompatibility 1.8
android.compileOptions.targetCompatibility 1.8
javaCompileOptions {
annotationProcessorOptions {
arguments = [ targetModuleName : 'Ship' ]
}
}
}
// 省略...
}
這次終于成功了跑起來了犹赖,我們試試插入吧!報錯:(我已經(jīng)習慣了)
com.raizlabs.android.dbflow.structure.InvalidDBConfiguration:
Model object: com.dthfish.ship.database.ShipProduct is not registered with a Database. Did you forget an annotation?
還好卷仑,通過錯誤我們看到說我們沒有在 Database 中注冊峻村,可是事實上我們已經(jīng)加了 @Table(database = ShipDatabase.class) 注解。實際上锡凝,是我們新生成的 ShipGeneratedDatabaseHolder 需要注冊粘昨。
FlowConfig flowConfig = new FlowConfig.Builder(this)
.addDatabaseHolder(ShipGeneratedDatabaseHolder.class)
.build();
FlowManager.init(flowConfig);
到這里,多 module 的使用已經(jīng)介紹完啦窜锯,我先去 Stackoverflow 上替外國友人解答下這個問題张肾!
外鍵
終于到這里了,原來解決完上邊的多 module 使用的問題其實已經(jīng)感覺篇幅有點長了锚扎,但是考慮到外鍵的重要性覺得還是有必要講一下的吞瞪。
升級數(shù)據(jù)庫:給表添加外鍵
事實上一上來就會有疑問:更新數(shù)據(jù)庫的時候添加普通的一列和添加外鍵一樣嗎?
對于我們的 bean 來說添加僅僅是添加了一個成員變量驾孔,但是更新數(shù)據(jù)庫就不一樣了芍秆,因為數(shù)據(jù)庫里面這些自定義的數(shù)據(jù)結(jié)構(gòu)是通過外鍵關聯(lián)的惯疙。好吧我們又回到了數(shù)據(jù)庫版本更新的問題,但是現(xiàn)在這個情況都不包含在之前提過的里面妖啥。下邊看一下我最終嘗試過后的結(jié)構(gòu):
@Table(database = AppDatabase.class)
public class Product extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String name;
@Column//(defaultValue = "10000")// 設置默認 100 塊錢霉颠,即使忘了錄入價格,我們賣出去也不吃虧
public long price = 10000L;// 分
@Column
public String manufacturer;
@ForeignKey(stubbedRelationship = true, saveForeignKeyModel = true)
public Category category;
}
@Migration(version = 5, database = AppDatabase.class)
public static class Migration5 extends AlterTableMigration<Product> {
public Migration5(Class<Product> table) {
super(table);
}
@Override
public void onPreMigrate() {
addForeignKeyColumn(SQLiteType.INTEGER, "category_id", FlowManager.getTableName(Category.class) +"(`id`) ");
}
}
- 在 Product 類中添加 category 字段荆虱,畢竟我們的產(chǎn)品是按品類劃分的
- 給 category 字段添加 @ForeignKey 注解蒿偎,注解里面的參數(shù),暫時先放一下怀读,一會說
- 編寫 Migration5诉位,修改版本號
雖然列了簡單的三步但是還是有值得思考的地方:onPreMigrate() 方法中的代碼該怎么寫?這個 category_id 哪里來的愿吹?
事實上 addForeignKeyColumn() 方法是在我寫版本升級的時候在 AlterTableMigration 類中發(fā)現(xiàn)的:
/**
* Add a column to the DB. This does not necessarily need to be reflected in the {@link TModel},
* but it is recommended.
*
* @param sqLiteType The type of column that pertains to an {@link SQLiteType}//添加的字段的類型
* @param columnName The name of the column to add. Use the "$Table" class for the specified table.//添加的列名
* @param referenceClause The clause of the references that this foreign key points to.//外鍵的指向
* @return This instance
*/
public AlterTableMigration<TModel> addForeignKeyColumn(SQLiteType sqLiteType, String columnName, String referenceClause) {
// 省略...
}
雖然注釋中有說各個參數(shù)的含義不从,但是事實上看了還是不知道怎么填寫,SQLiteType 中只有一些基本的類型犁跪,最終給我提示的還是 apt 生成的 Product_Table 類椿息。里面有這么一段:
@Override
public final String getCreationQuery() {
return "CREATE TABLE IF NOT EXISTS `Product`(`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `price` INTEGER, `manufacturer` TEXT, `category_id` INTEGER"+ ", FOREIGN KEY(`category_id`) REFERENCES " + com.raizlabs.android.dbflow.config.FlowManager.getTableName(com.dthfish.dbflowdemo.database.Category.class) + "(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION" + ");";
}
結(jié)果就是把里面的內(nèi)容截取出來當參數(shù)了,運行結(jié)果也令人滿意坷衍。ps:如果這里有誤請及時聯(lián)系我寝优,給我留言啊,我也是摸索出來的枫耳。
一對一外鍵
public void foreignKeyInsert(View view) {
// 為了保持每次的 Category 為同一個乏矾,其實是之前代碼設計的錯誤,沒有把 name 定為唯一
Category category = SQLite.select()
.from(Category.class)
.where(Category_Table.name.eq("meat"))
.querySingle();
if (category == null) {
category = new Category();
category.name = "meat";
}
Product product = new Product();
product.name = "P" + (System.currentTimeMillis() % 10000);
product.category = category;
product.save();
L.d("Insert: " + product.toString());
mLastInsertId = product.id;
}
private long mLastInsertId;
public void foreignKeyQuery(View view) {
// 查詢上次保存的 Product
List<Product> products = SQLite.select()
.from(Product.class)
.where(Product_Table.id.eq(mLastInsertId))
.queryList();
L.d("Query: " + products.toString());
}
兩個方法分別執(zhí)行迁杨,得到:
12-08 15:30:28.179 7615-7615/com.dthfish.dbflowdemo D/DBLog:
Insert: Product{id=6, name='P8158', price=10000, manufacturer='null', category=Category{id=1, name='meat'}}
12-08 15:30:30.419 7615-7615/com.dthfish.dbflowdemo D/DBLog:
Query: [Product{id=6, name='P8158', price=10000, manufacturer='null', category=Category{id=1, name='null'}}]
我們發(fā)現(xiàn) query 操作查出來的 category 字段钻心,只有 id,name 卻等于 null;修改如下:
private long mLastInsertId = 6L;// 6 為上邊 log 打印出來的 id
public void foreignKeyQuery(View view) {
List<Product> products = SQLite.select()
.from(Product.class)
.where(Product_Table.id.eq(mLastInsertId))
.queryList();
for (Product product : products) {
product.category.load();
}
L.d("Query: " + products.toString());
}
結(jié)果:
12-08 16:17:04.369 10814-10814/com.dthfish.dbflowdemo D/DBLog:
Query: [Product{id=6, name='P8158', price=10000, manufacturer='null', category=Category{id=1, name='meat'}}]
這里我們多執(zhí)行了一步操作铅协,調(diào)用了 Category 的 load() 方法捷沸,然后就查到了 id 為 1 的 Category 所有的信息了。
@ForeignKey(stubbedRelationship = true, saveForeignKeyModel = true, deleteForeignKeyModel = false)
public Category category;
解釋:
- 我們標注 @ForeignKey 的時候聲明了 stubbedRelationship = true狐史,這樣我們查詢的時候會僅僅查出 category 的主鍵痒给,當 load() 的時候才進一步查詢;
- 上面的例子當我們保存 Product 的時候骏全,事實上也沒有顯性的調(diào)用 category.save()苍柏,最終查詢的時候發(fā)現(xiàn)已經(jīng)插入了 name = "meat" 的 Category,這是因為 saveForeignKeyModel = true姜贡。有興趣的同學可以自己試一下 deleteForeignKeyModel = true;
一對多
@Table(database = AppDatabase.class)
public class Product extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String name;
@Column//(defaultValue = "10000")// 設置默認 100 塊錢试吁,即使忘了錄入價格,我們賣出去也不吃虧
public long price = 10000L;// 分
@Column
public String manufacturer;
@ForeignKey(stubbedRelationship = true, saveForeignKeyModel = true, deleteForeignKeyModel = false)
public Category category;
public List<Product> present;
public List<Product> getPresent() {
if (present == null || present.isEmpty()) {
present = SQLite.select()
.from(Product.class)
.where(Product_Table.name.like("PX%"))
.queryList();
}
return present;
}
}
簡單的帶過一下楼咳,就是添加一個 List熄捍,通過 get 方法去查詢律秃,僅此而已,甚至不需要升級版本號治唤。
事務
ContentProvider
剩下的幾個主題就不再寫了,有興趣的同學可以自己看下官方文檔糙申。
最后
說實話在寫這篇文章之前我自己寫過另一個 Demo宾添,但是那個就沒有這篇文章舉得例子這樣簡單明了。當然 DBFlow 的還有很多細節(jié)我沒有講到柜裸,包括但不限于加密缕陕,Rxjava這些的支持。正因為我自己在學習 DBFlow 的過程中也去找了相關文章疙挺,要么說的都比較簡單扛邑,要么版本不是最新的,最后還是自己看文檔進行嘗試铐然,總結(jié)一篇出來蔬崩。結(jié)果也是有很多意外收獲,總之在沒有開始寫的時候我知道篇幅會很長搀暑,但是實際上更長沥阳,所以最后有幾個想講的地方也就懶得完善了,哈哈自点!
如果有喜歡這篇文章的同學桐罕,請務必給我一個贊呀!這是對廣大寫博客的同學的最大的肯定桂敛!
BDFlow github 地址
Demo 地址
原文地址