ORMLite使い方メモ

Android で利用した場合の使い方をメモする。

#ORMLite とは
Android でも使える O/R マッパー。

メソッド名を見ればなんとなく使い方が分かるので、使いやすそうな印象。

#Hello World
##インストール
```groovy:build.gradle
dependencies {
    compile 'com.j256.ormlite:ormlite-android:4.48'
}
```

##実装
```java:TestTable.java
package com.example.ormlitesample;

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable
public class TestTable {
    
    @DatabaseField(id=true)
    private Long id;
    @DatabaseField
    private String code;
    @DatabaseField
    private String value;
    
    @SuppressWarnings("unused")
    private TestTable() {}
    
    public TestTable(Long id, String code, String value) {
        this.id = id;
        this.code = code;
        this.value = value;
    }

    @Override
    public String toString() {
        return "TestTable [id=" + id + ", code=" + code + ", value=" + value + "]";
    }
}
```

```java:MyDatabaseHelper.java
package com.example.ormlitesample;

import java.sql.SQLException;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;

import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;

public class MyDatabaseHelper extends OrmLiteSqliteOpenHelper {
    
    private static final String DATABASE_NAME = "ormlite.sample";
    private static final int DATABASE_VERSION = 1;
    
    public MyDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
        System.out.println("MyDatabaseHelper.onCreate()");
        try {
            TableUtils.createTable(connectionSource, TestTable.class);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
        System.out.println("MyDatabaseHelper.onUpgrade()");
    }
}
```

```java:MainActivity.java
package com.example.ormlitesample;

import java.sql.SQLException;

import android.os.Bundle;

import com.j256.ormlite.android.apptools.OrmLiteBaseActivity;
import com.j256.ormlite.dao.Dao;

public class MainActivity extends OrmLiteBaseActivity<MyDatabaseHelper> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try {
            Dao<TestTable, Long> dao = getHelper().getDao(TestTable.class);
            
            TestTable hoge = new TestTable(1L, "a", "A");
            dao.create(hoge);
            
            TestTable table = dao.queryForId(1L);
            
            System.out.println(table);
            
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
```

##動作結果
```text:
I/System.out(1935): MyDatabaseHelper.onCreate()
I/System.out(1935): TestTable [id=1, code=hoge, value=HOGE]
```

##説明
- データベースとマッピングするクラス（`TestTable`）は、 `@DatabaseTable` でアノテートする。
    - カラムとマッピングさせるフィールドは、 `@DatabaseField` でアノテートする。
    - フィールドの Getter/Setter は不要（private でも構わない）。
    - 引数なしのコンストラクタが必須（private でも構わない）。
- データベースアクセスには、 `OrmLiteSqliteOpenHelper` を継承したクラスを作成する。
    - `TableUtils.createTable()` を使うと、クラスに設定したアノテーションの情報をもとにテーブルを自動生成してくれる。
- アクティビティは `OrmLiteBaseActivity` を継承して作成する。
    - 型引数に、作成した `OrmLiteSqliteOpenHelper` のサブクラスを渡す。
    - `getHelper().getDao(Class)` で、デーブルごとの Dao を取得する。

###Dao 生成はコストが高い
Dao の生成はコストが高いらしく、１度生成したらできる限り使い回したほうが良いらしい。

> Building a DAO can be an expensive operation and for devices with limited resources (like mobile apps), DAOs should be reused if at all possible. 

[2.4 Setting Up the DAOs | ORMLite Documentation: 2. How to Use](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_2.html#DAO-Setup)

#OrmLiteBaseActivity を継承しない方法
```java:MainActivity.java
package com.example.ormlitesample;

import java.sql.SQLException;

import android.app.Activity;
import android.os.Bundle;

import com.j256.ormlite.android.apptools.OpenHelperManager;
import com.j256.ormlite.dao.Dao;

public class MainActivity extends Activity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try {
            MyDatabaseHelper helper = OpenHelperManager.getHelper(this, MyDatabaseHelper.class);
            
            Dao<TestTable, Long> dao = helper.getDao(TestTable.class);
            
            TestTable hoge = new TestTable(1L, "hoge", "HOGE");
            dao.create(hoge);
            
            TestTable table = dao.queryForId(1L);
            
            System.out.println(table);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        OpenHelperManager.releaseHelper();
    }
}
```

- `OpenHelperManager.getHelper()` メソッドで、ヘルパークラスを取得できる。
- この場合、アクティビティが削除されるときに `OpenHelperManager.releaseHelper()` を呼ばなければならない。

#SQLException をスローしない Dao を取得する
```java:
package com.example.ormlitesample;

import android.os.Bundle;

import com.j256.ormlite.android.apptools.OrmLiteBaseActivity;
import com.j256.ormlite.dao.RuntimeExceptionDao;

public class MainActivity extends OrmLiteBaseActivity<MyDatabaseHelper> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        RuntimeExceptionDao<TestTable, Long> dao = getHelper().getRuntimeExceptionDao(TestTable.class);
        
        TestTable table = dao.queryForId(1L);
        
        System.out.println(table);
    }
}
```

- `getRuntimeExceptionDao(Class)` で、 `SQLException` をスローしない Dao を取得できる。

#アノテーションで設定する
##@DatabaseTable
###tableName
- テーブル名を指定する。
- 省略した場合は、クラス名を小文字にしたものが使用される。

##@DatabaseField

全部書いてると大変なので、主要そうなのだけ。

その他の属性は [ORMLite Documentation: 2. How to Use](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_2.html#Class-Setup) を参照。

###columnName
- カラム名を指定する。

###dataType
- データベースに保存するときの型を指定する。
- [Persisted Data Types](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_2.html#Persisted-Types)

###defaultValue
- レコードを登録するときの初期値を設定する。

###width
- カラムのサイズを指定する。
- 主に文字列型のカラムのサイズを指定するのに使用する。
- 0 の場合はデータベースのデフォルトのサイズが使用される。

###canBeNull
- NULL 可項目にするかどうかを boolean で指定する。
- デフォルトは true。

###id
- プライマリキーであることを指定する。
- クラス内で、１つのフィールドにのみ設定できる。
- Dao の **ForId() などのメソッドは、id = true の項目が使用される。

###generatedId
- プライマリキーの値がデータベースによって自動生成されることを指定する。
- クラス内で、１つのフィールドにのみ設定できる。
- id 属性とは排他的に指定する。

###foreign
- true を指定すると、そのフィールドが他のテーブルの ID を参照していることを指定できる。
- Java クラス上は、参照するテーブルクラスをフィールドとして持つようにする。

```java:ForeignTable.java
package com.example.ormlitesample;

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable
public class ForeignTable {

    @DatabaseField(generatedId=true)
    private Long id;
    @DatabaseField
    private String value;
    
}
```

```java:TestTable.java
package com.example.ormlitesample;

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable
public class TestTable {
    
    @DatabaseField(generatedId=true)
    private Long id;
    @DatabaseField(foreign=true)
    private ForeignTable foreign;
}
```

###useGetSet
- true を設定すると、値の取得は Getter/Setter を通じて行われるようになる。
- デフォルトは、リフレクションを使ってフィールドを直接参照する。

###unknownEnumName
- DB から読み込んだレコードに enum の列挙子に存在しない値が存在した場合、どの列挙子にマッピングするかを文字列で指定する。
- デフォルトは、存在しない列挙子を読み込んだ場合、 SQLException がスローされる。

###persisted
- false を設定すると、そのフィールドはデータベースに保存されなくなる。

###unique
- true を設定すると、その項目にユニークキー制約が設定される。
- 複数項目でユニークキーを設定する場合は、 `uniqueCombo` を使う。

###uniqueCombo
- true を設定した複数の項目でユニークキー制約を設定する。

###uniqueIndexName
- 名前を指定してユニークキー制約を設定する。
- 同じ名前を設定したフィールドで１つのユニークキー制約を設定できる。

#QueryBuilder を使って検索する
##基本
```java:
package com.example.ormlitesample;

import java.sql.SQLException;
import java.util.List;

import android.os.Bundle;

import com.j256.ormlite.android.apptools.OrmLiteBaseActivity;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.QueryBuilder;

public class MainActivity extends OrmLiteBaseActivity<MyDatabaseHelper> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try {
            Dao<TestTable, Long> dao = getHelper().getDao(TestTable.class);
            
            QueryBuilder<TestTable, Long> queryBuilder = dao.queryBuilder();
            
            PreparedQuery<TestTable> preparedQuery = queryBuilder.where().eq("value", "hoge").prepare();
            
            List<TestTable> list = dao.query(preparedQuery);
            
            System.out.println(list);
            
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
```

- `Dao.queryBuilder()` でビルダーを取得する。
- ビルダーで検索条件を設定し、最後に `prepare()` メソッドで `PreparedQuery` のインスタンスを取得する。
- 取得した `PreparedQuery` を `Dao.query()` に渡す。

##AND, OR 条件を使う
###普通に連結する
```java:
QueryBuilder<TestTable, Long> builder = dao.queryBuilder();

builder.where().eq("value", "hoge")
               .or()
               .eq("value", "fuga");
```

```sql:
WHERE value = 'hoge'
   OR value = 'fuga' 
```

- `and()` または `or()` メソッドで AND, OR を指定できる。

###グループ化する
```java:
QueryBuilder<TestTable, Long> builder = dao.queryBuilder();

Where<TestTable, Long> where = builder.where();

where.or(
    where.and(
        where.eq("code", "hoge"),
        where.eq("value", "HOGE")
    ),
    where.and(
        where.eq("code", "piyo"),
        where.eq("value", "PIYO")
    )
);

PreparedQuery<TestTable> query = builder.prepare();
```

```sql:
WHERE (code = 'hoge' AND value = 'HOGE')
   OR (code = 'piyo' AND valud = 'PIYO')
```

- `Where` オブジェクトを使って、複雑な条件式を作ることができる（難読化しそうで恐い）。

その他にも `distinct()` とか `orderBy()` とか、様々なメソッドがビルダーに用意されている。
[3.4 QueryBuilder Capabilities | ORMLite Documentation: 3. Custom Statement Builder](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_3.html#QueryBuilder-Capabilities) この辺を見れば、だいたい使い方は分かる気がする。

#生 SQL を実行する
```java:MainActivity.java
package com.example.ormlitesample;

import java.sql.SQLException;

import android.os.Bundle;

import com.j256.ormlite.android.apptools.OrmLiteBaseActivity;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.GenericRawResults;
import com.j256.ormlite.field.DataType;

public class MainActivity extends OrmLiteBaseActivity<MyDatabaseHelper> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try {
            Dao<TestTable, Long> dao = getHelper().getDao(TestTable.class);
            
            String sql = "SELECT * FROM testtable WHERE value = ?";
            DataType[] dataTypes = {DataType.LONG_OBJ, DataType.STRING};
            
            GenericRawResults<Object[]> result = dao.queryRaw(sql, dataTypes, "HOGE");
            
            for (Object[] row : result) {
                for (Object column : row) {
                    System.out.println("class=" + column.getClass() + ", value=" + column);
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
```

```text:LogCat出力
I/System.out(12073): class=class java.lang.Long, value=1
I/System.out(12073): class=class java.lang.String, value=HOGE
```

- `Dao.queryRow()` という名前のメソッドがいくつかオーバーロードされており、これらは生の SQL を実行できる。
- COUNT を取りたいときとかは、 `queryRawValue()` という long 値を返すメソッドが用意されている。

##生 SQL を書いたけど、結局 Java クラスにマッピングできるように SELECT している場合
複雑な JOIN とかしたけど、結局 SELECT しているのは１つのテーブルの項目だけで、そのままクラスにマッピングして欲しいときとかは、以下のようにするといいと思う。

```java:
package com.example.ormlitesample;

import java.sql.SQLException;

import android.os.Bundle;

import com.j256.ormlite.android.apptools.OrmLiteBaseActivity;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.GenericRawResults;
import com.j256.ormlite.dao.RawRowMapper;

public class MainActivity extends OrmLiteBaseActivity<MyDatabaseHelper> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try {
            Dao<TestTable, Long> dao = getHelper().getDao(TestTable.class);
            
            String sql = "SELECT * FROM testtable";
            RawRowMapper<TestTable> mapper = dao.getRawRowMapper();
            
            GenericRawResults<TestTable> result = dao.queryRaw(sql, mapper);
            
            for (TestTable row : result) {
                System.out.println(row);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
```

- `queryRow()` の第二引数に、 dao から取得しておいた TestTable クラス用の `RawRowMapper` を渡す。

#RoboGuice と連携する
```java:MyDatabaseHelperProvider.java
package com.example.ormlitesample;

import javax.inject.Inject;

import roboguice.activity.event.OnCreateEvent;
import roboguice.activity.event.OnDestroyEvent;
import roboguice.event.Observes;
import roboguice.inject.ContextSingleton;
import android.app.Activity;

import com.j256.ormlite.android.apptools.OpenHelperManager;
import com.j256.ormlite.dao.RuntimeExceptionDao;

@ContextSingleton
public class MyDatabaseHelperProvider {

    @Inject
    private Activity activity;
    
    private MyDatabaseHelper helper;
    private RuntimeExceptionDao<TestTable, Long> testTableDao;
    
    public void onActivityCreate(@Observes OnCreateEvent event) {
        this.helper = OpenHelperManager.getHelper(this.activity, MyDatabaseHelper.class);
    }
    
    public void onActivityDestroy(@Observes OnDestroyEvent event) {
        OpenHelperManager.releaseHelper();
    }
    
    public RuntimeExceptionDao<TestTable, Long> getTestTableDao() {
        if (this.testTableDao == null) {
            this.testTableDao = this.helper.getRuntimeExceptionDao(TestTable.class);
        }
        
        return this.testTableDao;
    }
}
```

```java:MainActivity.java
package com.example.ormlitesample;

import java.util.List;

import javax.inject.Inject;

import roboguice.activity.RoboActivity;
import roboguice.inject.ContentView;
import android.os.Bundle;

import com.j256.ormlite.dao.RuntimeExceptionDao;

@ContentView(R.layout.activity_main)
public class MainActivity extends RoboActivity {
    
    @Inject
    private MyDatabaseHelperProvider provider;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        RuntimeExceptionDao<TestTable, Long> dao = this.provider.getTestTableDao();
        
        List<TestTable> list = dao.queryForAll();
        
        for (TestTable t : list) {
            System.out.println(t);
        }
    }
}
```

- `MyDatabaseHelper` を供給するクラスを `@ContextSingleton` スコープで定義する。
- `@Observes` を使うことで、 `onDestory()` 時のリリース漏れが無いようにしている（これで使い回しがしやすくなる気がする）。

#参考
- [ORMLite Documentation: ORMLite](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite.html#Top)