androidデータベースの基本

#概要
今回はハッカソンでandroid開発をしたい！けどデータ管理が分からないという方向けの簡単なハンズオンです。自身が理論に深く精通しているわけではないのでミス等々あるかと思いますが、メモ程度で見ていただけたらと思います。

#目次
1. データベースとは
2. androidにおけるデータベースの扱い(DatabaseHelper/追加/取り出し/削除)
3. データベースの中身を見る方法
4. Q&A

#データベースとは
アプリでせっかくデータを作成しても、次起動したときには消えてしまう...
そんな悩みを解決するためのツールがデータベースです！

データベースにも色々と種類があるのですが、androidstudioにはsqlite3というデータベースが標準で用意されているので今回はsqlite3の使い方を解説します！

#androidにおけるデータベースの扱い
理論は置いておいて流れはこんな感じ...

(1) DetabaseHelperクラスを作る
基本はDetabaseHelperと呼ばれるクラスを介して、全てのデータ管理を行います！

```DetabaseHelper.java
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper
{
    private static final String DATABASE_NAME = "hoge";
    private static final int DATABASE_VERSION = 1;

    public DatabaseHelper(Context context)
    {
        super(context,DATABASE_NAME,null,DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db)
    {
        /*ここにテーブルを作る処理を書く*/
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldversion ,int newversion)
    {
    }
}
```

-解説-
基本的にはコピペで大丈夫です！

+ ```DATABASE_NAME = "hoge"```のhogeの部分にデータベース本体の名前(任意)を付けてください!

+ onCreate()は関数で、ここにテーブル(後述)を作る処理を書きます

+ onUpgrade()は基本必要ないですが、書かないとエラーが出るのでいちおう残しておいてください！


(2) テーブルを作る
テーブルとは、データを管理する表の事です。データベースの中に複数作られます。
例えば、ユーザー情報を管理するテーブル(User)を作るとすると以下の様なイメージです。

| _id| name | age |
|:---|-----:|:----|
| 1  |  Taro|18  |
| 2  | Kevin|16  |

この表をjavaで書くときは先ほどのonCreate()関数内に以下の様に書きます！

```DatabaseHelper.java
@Override
    public void onCreate(SQLiteDatabase db)
    {
        /*ここにテーブルを作る処理を書く*/
        StringBuilder sb = new StringBuilder();
        sb.append("CREATE TABLE IF NOT EXISTS ");
        sb.append("Hoge" + " (");
        sb.append("_id INTEGER PRIMARY KEY,");
        sb.append("name TEXT,"),
        sb.append("age INTEGER)");
        db.execSQL(sb.toString());
    }
```
-解説-

+ ```sb.append("CREATE TABLE IF NOT EXISTS ");```で、既にUserテーブルが存在する場合は以下の処理をパスしてくれます！

+ ```sb.append("Hoge" + " (");```のHogeの部分にテーブルの名前を書きます。(今回ならUser)

+ ```sb.append("_id INTEGER PRIMARY KEY,")```に関しては、"PRIMARY KEY"の記述によって、データ入力時にidに値を入力しなくても自動でidを埋めてくれるようになります！

+ ```INTEGER,TEXT``` についてはデータの型になります。age(年齢),idは整数値を扱うためINTEGERを、name(名前)は文字列を扱うためTEXTを用います！

+ ``` db.execSQL(sb.toString());``` については、「テーブルを作ってね」という指令をデータベースに送るためのものですが、小難しいのでとりあえずおまじない程度だと思ってください。


(3) データを格納する
既に上記のUserテーブルが存在している前提で書きます！
まず、どこの画面からデータベースにデータを入れるかを考えます。
例えば、ガチャ結果の履歴を残すならガチャの結果画面(もしくはその前の画面)からデータを入れます。

MainActivity内のボタンを押した際にデータを入れると仮定すると、以下の様にデータを格納できます。

```MainActivity.java

@Override
protected void onCreate(){
    //...
    Button button = findViewById(R.id.hoge)
    button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                DatabaseHelper helper = new DatabaseHelper(Hoge.this);
                SQLiteDatabase db = helper.getWritableDatabase();
                try{
                    String sql = "INSERT INTO User (name,age) VALUES ('Taro',18);"
                    SQLiteStatement stmt = db.compileStatement(sql);
                    stmt.executeInsert();
                }finally{
                    db.close();
                }
           } 
    });
}

```
-解説-

+ ```DatabaseHelper helper = new DatabaseHelper(Hoge.this);```の部分で先ほど作ったDatabaseHelperクラスのオブジェクトを作っています！今後はこのhelperという変数を用いてデータベースとのやり取りを行います。Hogeの部分にはアクティビティ名を入れてください！(今回ならMainActivity)

+ ```SQLiteDatabase db = helper.getWritableDatabase();```の部分はデータベースをどう開くか(書き込みモード/読み込みモード)を指定していますが、おまじない程度だと思ってください。

+ ```String sql = "INSERT INTO User (name,age) VALUES ('Taro',18);"```の部分は、Userテーブルにデータを一件追加(INSERT)する文字列を作る処理を書いています！

+ ```SQLiteStatement stmt = db.compileStatement(s);``` ,``` stmt.executeInsert();```の部分は、「先ほど作ったINSERT文」が間違ってないかチェックし、データベースに渡す処理をしていますが、これも今はおまじない程度だと思ってもらってかまいません.

※~~INSERT文に_idを指定していないのは、PRIMARY_KEYの影響です。_idが無い場合自動で_idの値を登録してくれます！~~(修正: _idは特殊な列で、_idを指定しなかった場合、既に使われているid値を考慮して自動でid値を追加してくれます。)


(4) データを読みだす
ここまでで、最初に示したUserテーブル(Taro,Kevinのデータ登録済み)が完成したと思うので、今度はこれを読みだしてみます！

例えば、MainActivityのレイアウト画面にデータベースから読み出したデータを表示する場合を考えます。この時、「MainActivityのonResume()関数にデータ読み出しの処理を書く」ことをお勧めします！！
まず、onResume()関数を作りましょう！！！(onCreate()関数に書いても良いのですが、この場合、例えば違う画面に遷移し、そこでデータを入力した場合、元の画面に戻ってきたときに先ほど入れたデータが反映されない事態に陥ります...)

```MainActivity.java

@Override
protected void onCreate(){
    //...
}

@Override
protected void onResume(){
    /*新しく作成*/
    DatabaseHelper helper = new DatabaseHelper(Hoge.this);
    SQLiteDatabase db = helper.getReadableDatabase();
    try{
        String sql = "SELECT name,age FROM User";
        cursor = db.rawQuery(sql,null);
        /*以下でnameとageを取得していいます*/
        while(cursor.moveToNext()){
            name = cursor.getString(cursor.getColumnIndex("name"));
            age = cursor.getInt(cursor.getColumnIndex("age"));
        }
    }finally{
        db.close();
    }
}
```
-解説-

+ ```DatabaseHelper helper = new DatabaseHelper(Hoge.this);```の部分で先ほど同様DatabaseHelperクラスのオブジェクトを作っています！helperという変数を用いてデータベースとのやり取りを行います。Hogeの部分にはアクティビティ名を入れてください！(今回ならMainActivity)

+ ```SQLiteDatabase db = helper.getReadableDatabase();```の部分も、さきほどのgetWritableDatabase()と似ているのですが、今回は読み込みしかしないのでgetReadableDatabase()と書きます。

+ ```String sql = "SELECT name,age FROM User";```の部分は、Userテーブルから全てのデータを取り出し、nameとage部分だけ切り出します(nameだけ欲しい場合は、SELECTの後ろにnameのみ書いて下さい。)

+ ```cursor = db.rawQuery(sql,null);```では、上のSELECT文をデータベースに渡す処理をしています。(おまじない)

+ 上のサンプルコード中のwhile内で、nameとageを取得しています。今回は、「Taro,Kevin」の2件のデータを想定しているのでwhile文が必要ですが、1件のデータを扱うのであればwhile文は無くても大丈夫です。(cursor.moveToNext()は必要)
具体的には、whileループ1周目でTaroのデータについてnameとageを取得しています。コードの通りですが、nameなど文字列を取得する時には```cursor.getString(cursor.getColumnIndex(列の名前))```を書き、ageなど整数値を取得する時には```cursor.getInt(cursor.getColumnIndex(列の名前))```と書きます。whileループ2周目はKevinのデータについて同様にデータの取り出しが行われます。

(5) データの削除
次に登録したTaroのデータをデータベースから削除する方法を書きます。

例えば、TODOリストが表示されるListActivityで削除ボタンを押した時にデータベースからデータを削除する処理を考えると以下の様に書けます。

```ListActivity.java

@Override
protected void onCreate(){
    //...
    Button button = findViewById(R.id.hoge)
    button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                DatabaseHelper helper = new DatabaseHelper(Hoge.this);
                SQLiteDatabase db = helper.getWritableDatabase();
                try{
                    String sql = "DELETE FROM User where _id = 1"
                    SQLiteStatement stmt = db.compileStatement(sql);
                    stmt.executeUpdateDelete();
                }finally{
                    db.close();
                }
           } 
    });
}
```
-解説-

+  ```DatabaseHelper helper = new DatabaseHelper(Hoge.this);```の部分で先ほど同様DatabaseHelperクラスのオブジェクトを作っています！helperという変数を用いてデータベースとのやり取りを行います。Hogeの部分にはアクティビティ名を入れてください！(今回ならListActivity)

+ ```SQLiteDatabase db = helper.getWritableDatabase();```の部分は、今回は書き込み処理にあたるため、getWritableDatabase()と記述します。

+ ```String sql = "DELETE FROM User where _id = 1"```の部分は、DELETEの後ろに削除作業を行いたいテーブルを指定し(今回ならUserを指定し)、削除したいデータの条件をWHEREの後に書き、条件に合うデータを削除する文字列を生成しています。(今回は、_idが1のデータを削除)

+  ```SQLiteStatement stmt = db.compileStatement(sql);```,  ```stmt.executeUpdateDelete(); ```の部分は、上で作った文字列にミスが無いかを確認し、データベースに「削除をお願いします！」と要求している部分になります。(おまじない)

※WHERE句は、データの取り出しにも使えます！例えば、```WHERE age >= 12 ```と書けば、データベースから12歳以上のデータのみを探し、(削除/取り出し)を行うことができます。

#データベースの中身を見る方法
1.<url>https://sqlitebrowser.org/</url>から「DB Browser for SQLite」というアプリをダウンロードしてください。(Windows32bit/64bit版,Mac版がありますので対応するものを選んでください。)

※現状3.11.2が最新バージョンのようです (2019.3.23)
2.ダウンロードしたファイルを展開し、ウィザードに沿ってインストールしてください。

3.androidstudioから「デバイス・ファイル・エクスプローラ」(デフォルトだと恐らく右下に存在)を選択し、「data」> 「data」 > 「アプリのパッケージ名」 > 「databases」とフォルダを辿り、中の「hoge.db」ファイルを展開して下さい。(hoge部分は、この記事で最初に設定したDATABASE_NAMEになっているかと思います。)
※展開したファイルが恐らく文字化けしているので、先ほどインストールしたアプリで開きます。

4.開いたファイルを右クリックし、「エクスプローラで表示」を選択します。

5.エクスプローラで、「hoge.db」ファイルを右クリックし、「プログラムから開く」 > 「DB Browser for SQLite」を選択します。

6.「DB Browser for SQLite」内の「Browse Data」を見ると、テーブルにデータが格納されているか確認することができます！


#Q&A
(1): tryとfinallyってなんですか?
普通コード内でエラーが起きた時にはそこで実行が止まり、そのあとの処理は行われません。
しかし、try内でエラーが出た場合はfinallyに飛び、finallyの処理だけは行ってくれます。
データベースにはopen/closeという概念が存在し、データを入れる/取り出すという処理を行うとき以外はclose状態を保つ必要があります。finally内にclose処理を書いておくことでclose状態を保つことができます。(恐らく、get(Writable/Readable)Database()がdatabaseのopen処理)

(2): データベースのテーブルの要素を変えたのに反映されません
データベースのテーブルは、DatabaseHelperに書きますが、このクラスが持つonCreate関数部分は、データベースが存在しない時にしか呼ばれません。よって、一度データベースの中身をリセットする必要があります。
データベースのリセットのやり方は2つあり、「エミュレータ内のアプリを削除する方法」と「「data」> 「data」 > 「アプリのパッケージ名」>「databases」と辿った後、中にあるhoge.dbを右クリックして削除する方法」があります。

(3): データベースにデータを入れたはずなのに「DB Browser for SQLite」にデータが入っていません。
コード上のミスでデータがそもそも入っていない、もしくは「データベースの同期化」ができていない可能性があります。「「data」> 「data」 > 「アプリのパッケージ名」と辿った後、中にある「databases」フォルダを右クリックして同期化したあと、「DB Browser for SQLite」で中身を確認してみてください。

(4): 既にあるデータの編集はできないんですか?
できます！しかし、既にあるデータを削除して新しく作成するという処理を書けば同等の処理が可能です。今回はハッカソンレベルを想定し、なるべく覚える事を減らす事を意識しているためここでは敢えて説明しません。

※ほかにも質問等あったら随時追加します。