AndroidのためのSquiDB


やっぱりアプリつくるとデータをどう持つか考える必要がありますね。Androidアプリの内部DBにデータを持つことにしたので、SQLiteのライブラリとして[SquidDB](https://github.com/yahoo/squidb)を使ってみました。バージョンは3.2.3です。


## 設定

app/build.gradleにSquidDBを追加します。プロジェクト全体のbuild.gradleではないのでご注意を。

ついでにInjectionも追加します。

```build.gradle
apply plugin: 'kotlin-kapt'

dependencies {
    ...
    // squidb
    implementation "com.yahoo.squidb:squidb:3.2.3"
    implementation 'com.yahoo.squidb:squidb-annotations:3.2.3'
    implementation 'com.yahoo.squidb:squidb-android:3.2.3'
    kapt 'com.yahoo.squidb:squidb-processor:3.2.3'
    
    // injection
    compile "com.google.dagger:dagger:2.8"
    kapt "com.google.dagger:dagger-compiler:2.8"
}
```


## テーブルスキーマの定義

Personテーブルの定義を作ります。ここで定義したclassName(Personのこと)が、他のクラスから参照されるクラス名になります。ビルドするとSquiDBがジェネレイトしてくれます。

```kotlin:PersonSpec.kt
package com.example.myfirstapp.database

import com.yahoo.squidb.annotations.ColumnSpec
import com.yahoo.squidb.annotations.TableModelSpec

@TableModelSpec(className="Person", tableName="people")
class PersonSpec {
    private val firstName : String? = null

    private val lastName : String? = null

    @ColumnSpec(name = "creationDate")
    private val birthday : Long = 0
}
```

### 使える属性
使える属性は次のとおりです。意外なことにDate型は使えません。

* Long
* Integer
* String
* Boolean
* Double
* byte\[\] (Blob)


## データベース設定

SquidDatabaseを継承して、データベース全体の定義を行います。
createOpenHelperがちょっとめんどうです。今回はこのクラスを利用する際にコンストラクタとしてcontextを追加することにします。

```kotlin:MyDatabase.kt
package com.example.myfirstapp.database

import android.content.Context
import com.yahoo.squidb.android.AndroidOpenHelper
import com.yahoo.squidb.data.ISQLiteDatabase
import com.yahoo.squidb.data.ISQLiteOpenHelper
import com.yahoo.squidb.data.SquidDatabase
import com.yahoo.squidb.sql.Table
import com.example.myfirstapp.database.Person

import javax.inject.Inject

class MyDatabase @Inject constructor(private  val context: Context): SquidDatabase(){
    val VERSION : Int = 1
    val NAME : String = "my-database.db"

    override fun getName(): String {
        return NAME
    }

    override fun getTables(): Array<Table> {
        return arrayOf(Person.TABLE)
    }

    override fun onUpgrade(db: ISQLiteDatabase?, oldVersion: Int, newVersion: Int): Boolean {
        return false
    }

    override fun getVersion(): Int {
        return VERSION
    }

    override fun createOpenHelper(databaseName: String?, delegate: OpenHelperDelegate?, version: Int): ISQLiteOpenHelper? {
        return AndroidOpenHelper(context,name,delegate,version)
    }

}
```

## 挿入する

これでデータベースをする準備ができました。さっそくレコードを追加してみましょう。適当なActivity上でやってみます。

```kotlin
val db: MyDatabase = MyDatabase(this)
val p: Person = Person()
p.firstName = "Sam"
p.lastName = "Bosley"
p.birthday = System.currentTimeMillis()
db.persist(p)
```

persistを実行したあとは、 `p.id` が設定されています。


## クエリーを組み立てる
「select * from person where filest_name = "Sam"」的なクエリーです。`=`の他にも、`>`, `<`, `and`, `or`, `join`, `order by`, `limit`, `offset`などができます。

```kotlin
val q: Query = Query.select().where(Person.FIRST_NAME.eq("Sam"))
```

## 検索する

取得結果が複数ある場合の検索です。

```kotlin
val cursor: SquidCursor<Person> = db.query(Person::class.java, q)
try{
    val selectedPerson : Person = Person()
    while(cursor.moveToNext()){
       selectedPerson.readPropertiesFromCursor(cursor)
       // 検索結果を使った処理
    }
}finally {
    cursor.close()
}
```

取得結果が１件しかないとか、先頭１件だけつかうときはこっちでOK。

```kotlin
val person = db.fetchByQuery(Person::class.java, q)
```

## 更新する

1レコードだけ更新するやり方は、挿入とほぼ同じです。内部にセットされているIDを使って更新されます。

```kotlin
person.firstName = "Samuel"
db.persist(person)
```

詳細は割愛しますが、条件文を使って複数レコードを一度に更新することもできます。

## 削除する

削除は、IDをつかって行います。

```kotlin
db.delete(Person::class.java, person.id)
```

更新と同様に、条件文を使って複数レコードを一度に削除することもできます。

## no such tableエラーになったら

アプリをエミュレータや実機で動かしていると、「no such table」エラーになる場合があります。アプリの初期インストールのときは自動的にテーブルが生成されますが、あとからテーブルを追加する場合は明示的に生成する必要があるためです。

もし（まだリリースしていない）開発中の場合だったら、エミュレータ上のアプリをアンインストールしましょう。

リリース後だったら、MyDatabaseで[アップグレード時の処理](https://github.com/yahoo/squidb/wiki/Implementing-database-upgrades)を定義します。

カラム追加の場合も同様です。


```kotlin:MyDatabase.kt
    @Override
    protected int getVersion() {
        // これからリリースするアプリのバージョンが２
        return 2;
    }

    @Override
    protected boolean onUpgrade(ISQLiteDatabase db, int oldVersion, int newVersion) {
        switch(oldVersion) {
        case 1:
            // テーブルを追加する
            tryCreateTable(NewTable.TABLE);
        }
        return true;
    }
```


## 備考
公式ドキュメントの他に、こちらのレポジトリを参考にしました。
* [kyryloz/android-budget-app](https://github.com/kyryloz/android-budget-app)
* [vaibhavsonu/ModernAlarm](https://github.com/vaibhavsonu/ModernAlarm)