Android SDKのDB操作を便利にしたい

AndroidのDB操作が不便です。(文字列ベースなのでEclipseの補完が使えない等)

通常の書き方(rawQueryの場合)

Cursor cursor = db.rawQuery("select * from employee where _id in (?, ?);", new String[] {"2", "3"});
List<Employee> list = new ArrayList<Employee>();
while(cursor.moveToNext()) {
	Employee emp = new Employee();
	int id = cursor.getInt(cursor.getColumnIndex("_id"));
	String name = cursor.getString(cursor.getColumnIndex("name"));
	emp.setId(id);
	emp.setName(name);
	list.add(emp);
}

流れるインタフェースで書きたい

Slim3やS2ContainerのORマッパーS2JDBCのような、流れるインターフェースで書けたら幸せ。

S2JDBCの流れるインターフェース
List<Employee> results = jdbcManager.from(Employee.class)
                             .join("department")
                             .where("id in (? , ?)", 11, 22)
                             .orderBy("name")
                             .getResultList();

Seasar2 - S2JDBC - チュートリアル

Slim3のソースとS2JDBCのリファレンスを見ながら、もどきを作ってみましょう。

SQLiteDatabaseのラッパークラス

public class SQLiteDatabaseWrapper {

	private SQLiteDatabase mDatabase;

	public SQLiteDatabaseWrapper(SQLiteDatabase writableDatabase) {
		mDatabase = writableDatabase;
	}

	public <M> AutoSelect<M> from(Class<M> fromClass) {
		return new AutoSelect<M>(fromClass, mDatabase);
	}
//以下略

クエリを処理するAutoSelect

とりあえずwhereだけ実装。(joinとかorderbyとかは後回し)

public class AutoSelect<M> {
	private Class<M> fromClass;
	private SQLiteDatabase mDatabase;
	private String criteria;
	private String[] params;

	public AutoSelect(Class<M> fromClass, SQLiteDatabase mDatabase) {
		this.fromClass = fromClass;
		this.mDatabase = mDatabase;
	}

	public Cursor getCursor() {
		ModelMeta<M> meta = SQLiteDatabaseWrapper.getModelMeta(fromClass);

		return mDatabase.query(meta.getTableName(), null, criteria, params, null, null, null);
	}

	public int getCount() {
		return getCursor().getCount();
	}
	
	public AutoSelect<M> where(String criteria, String... params) {
		this.criteria = criteria;
		this.params = params;
		return this;
	}
	
	public AutoSelect<M> where(String criteria, int... intParams) {
		params = new String[intParams.length];
		for (int i = 0; i < intParams.length; i++) {
			params[i] = String.valueOf(intParams[i]);
		}
		this.criteria = criteria;
		return this;
	}

	public List<M> getResultList() {
		List<M> list = new ArrayList<M>();
		Cursor cursor = getCursor();
		
		while (cursor.moveToNext()) {
			ContentValues values = new ContentValues();
			
			String[] columnNames = cursor.getColumnNames();
			for (int i = 0; i < columnNames.length; i++) {
				String value = cursor.getString(cursor.getColumnIndex(columnNames[i]));
				values.put(columnNames[i], value);
			}
			ModelMeta<M> meta = SQLiteDatabaseWrapper.getModelMeta(fromClass);
			list.add(meta.contentValuesToModel(values));
		}
		return list;
	}

}

ModelMetaの実装クラス。

Slim3のModelMetaを参考にしました。(DatastoreのEntityを、ContentValuesで置き換えたイメージ)

public class EmployeeMeta extends ModelMeta<Employee> {

	@Override
	public String getTableName() {
		return "hoge";
	}
	
	@Override
	public ContentValues modelToContentValues(Object model) {
		Employee emp = (Employee) model;
		ContentValues values = new ContentValues();
		values.put("_id", emp.get_id());
		values.put("name", emp.getName());
		values.put("date", emp.getDate());
		return values;
	}

	@Override
	public Employee contentValuesToModel(ContentValues values) {
		Employee emp = new Employee();
		emp.setId(values.getAsInteger("_id"));
		emp.setName(values.getAsString("name"));
		emp.setDate(values.getAsString("date"));
		return emp;
	}

}

Androidで流れるインターフェース!

上記のクラスにより、流れるインターフェースもどきが作れました。

List<Employee> list = db.from(Employee.class).where("_id in (?, ?)", 1, 2).getResultList();

上記は全部手動で実装しましたが、Slim3のようにModelクラスの定義に応じて、ModelMetaやテーブルのCREATE構文が自動生成されたら使い勝手よくなりそうですね。APTを勉強するいい機会です。

※getModelMetaの部分はハリボテなので見せられない