面倒臭いを簡単にする
AndroidでSQLiteDatabaseを使うとき、いろいろと面倒臭いことがあります。
特に標準でO/Rマッパー無いので自分でマッピングしなければなりません。
巷にいろいろなライブラリがありますが、どれも重量級で、
ちょっと機能を使いたいだけでもいろいろやらないと行けないです。
SELECT、INSERT、UPDATE、DELETEのそれぞれの小さいコードを
APT(Annotation Processing Tool)で自動生成するツール、CatHandsGendroidを作りました。
どう簡単したいか
例えば、次のようなデータクラスがあるとします。
public class UserModel {
public enum Role {
PROGRAMMER, DESIGNNER, MANAGER
}
public enum Authority {
USER, ADMIN
}
private Long id;
private String username;
private String nickname;
private String team;
private Role role;
private Date createdAt;
private List<String> tags;
/* GetterとSetterは省略 */
}
このモデルクラスには次のような定義やメソッドが欲しくなりますよね。
/** CREATE文のSQL */
public static final String SQL_CREATE_TABLE = "CREATE TABLE userModel(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,nickname TEXT,team TEXT,role TEXT,createdAt INTEGER,tags BLOB,authority INTEGER,blob BLOB,booleanData INTEGER,byteData INTEGER,charData INTEGER)";
/** id指定でCursorを取得するメソッド */
public static Cursor findCursorById(SQLiteDatabase db, java.lang.Long id) {
String selection = "id=?";
String[] selectionArgs = new String[]{Accessor.toString(id)};
return db.query(TABLE_NAME, COLUMNS_ARRAY, selection, selectionArgs, null, null, null);
}
/** id指定でオブジェクトを取得するメソッド */
public static UserModel findById(SQLiteDatabase db, java.lang.Long id) {
Cursor cursor = findCursorById(db, id);
UserModel model = (cursor.moveToNext()) ? readCursorByIndex(cursor) : null;
cursor.close();
return model;
}
/** UserModelのインスタンスを作り、Cursorからの値を設定して返すメソッド */
public static UserModel readCursorByIndex(Cursor cursor) {
UserModel result = new UserModel();
readCursorByIndex(cursor, result);
return result;
}
/** CursorからUserModelのインスタンスに値を読み出す値を設定して返すメソッド */
public static void readCursorByIndex(Cursor cursor, UserModel dest) {
dest.setId(ACCESSOR_ID.readFromCursor(cursor, 0));
dest.setUsername(ACCESSOR_USERNAME.readFromCursor(cursor, 1));
dest.setNickname(ACCESSOR_NICKNAME.readFromCursor(cursor, 2));
dest.setTeam(ACCESSOR_TEAM.readFromCursor(cursor, 3));
dest.setRole(ACCESSOR_ROLE.readFromCursor(cursor, 4));
dest.setCreatedAt(ACCESSOR_CREATED_AT.readFromCursor(cursor, 5));
dest.setTags(ACCESSOR_TAGS.readFromCursor(cursor, 6));
dest.setAuthority(ACCESSOR_AUTHORITY.readFromCursor(cursor, 7));
dest.setBlob(ACCESSOR_BLOB.readFromCursor(cursor, 8));
dest.setBooleanData(ACCESSOR_BOOLEAN_DATA.readFromCursor(cursor, 9));
dest.setByteData(ACCESSOR_BYTE_DATA.readFromCursor(cursor, 10));
dest.setCharData(ACCESSOR_CHAR_DATA.readFromCursor(cursor, 11));
}
/** モデルクラスの値をContentValuesに設定するメソッド */
public static void putToContentValues(ContentValues dest, UserModel model,
boolean withPrimaryKey) {
if (withPrimaryKey) {
ACCESSOR_ID.putToContentValues(dest, "id", model.getId());
}
ACCESSOR_USERNAME.putToContentValues(dest, "username", model.getUsername());
ACCESSOR_NICKNAME.putToContentValues(dest, "nickname", model.getNickname());
ACCESSOR_TEAM.putToContentValues(dest, "team", model.getTeam());
ACCESSOR_ROLE.putToContentValues(dest, "role", model.getRole());
ACCESSOR_CREATED_AT.putToContentValues(dest, "createdAt", model.getCreatedAt());
ACCESSOR_TAGS.putToContentValues(dest, "tags", model.getTags());
ACCESSOR_AUTHORITY.putToContentValues(dest, "authority", model.getAuthority());
ACCESSOR_BLOB.putToContentValues(dest, "blob", model.getBlob());
ACCESSOR_BOOLEAN_DATA.putToContentValues(dest, "booleanData", model.getBooleanData());
ACCESSOR_BYTE_DATA.putToContentValues(dest, "byteData", model.getByteData());
ACCESSOR_CHAR_DATA.putToContentValues(dest, "charData", model.getCharData());
}
/** INSERTするメソッド */
public static long insert(SQLiteDatabase db, UserModel model) {
ContentValues values = new ContentValues();
putToContentValues(values, model, false);
long key = db.insert(TABLE_NAME, null, values);
model.setId((long)key);
return key;
}
/** UPDATEするメソッド */
public static int update(SQLiteDatabase db, UserModel model) {
ContentValues values = new ContentValues();
String whereClause = "id=?";
String[] whereArgs = new String[] {
ACCESSOR_ID.stringValue(model.getId())
};
putToContentValues(values, model, false);
return db.update(TABLE_NAME, values, whereClause, whereArgs);
}
/** DELETEするメソッド */
public static int delete(SQLiteDatabase db, Long key) {
String whereClause = "id=?";
String[] whereArgs = new String[] {
ACCESSOR_ID.stringValue(key)
};
return db.delete(TABLE_NAME, whereClause, whereArgs);
}
ちょっと小出しにしたので数が増えてしまいましたが、
簡単にはSELECT、INSERT、UPGRADE、DELETEのそれぞれのメソッドが欲しくなると思います。
CatHandsGendroidでは、これらを全てAPTにより自動生成してくれます。
APTによる生成であるため、生成されたコードを見ればどういった動きをしているのかも直ぐにわかります。
使い方:APTの設定編
まずAPTを使用するため、それ用の設定をbuild.gradleに追加します。
[ルート]/build.gradleのbuildscriptのブロックに"classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'"を追加してください。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
}
次に[ルート]/[app]/build.gradleに以下のコードを追加してください。
resourcePackageNameの部分は適宜、自分のプロジェクトに合わせて設定してください。
apply plugin: 'com.neenbedankt.android-apt'
apt {
arguments {
androidManifestFile variant.processResources.manifestFile
resourcePackageName 'com.myproject.package'
}
}
ここまででAPTが使えるようになりました。
最後にCatHandsGendroidをdependanciesに追加します。
dependencies {
apt 'net.cattaka:cathandsgendroid-apt:0.4.4'
compile 'net.cattaka:cathandsgendroid-core:0.4.4'
}
最新のバージョンは http://mvnrepository.com/artifact/net.cattaka/ にて確認することができます。
以上でCatHandsGendroidを使う設定が完了です。
使い方:モデルクラスの作成編
使い方は簡単、目的であるモデルクラスに@DataModelアノテーションを追加し、主キーを指定するだけです。
@DataModelアノテーションには、find属性に検索に使う項目を指定したり、uniqueであるべき項目を指定することができます。
主キーとする項目には@DataModelAttrs(primaryKey = true)アノテーションを付与します。
@DataModel(find = {
"id", "username", "team:role+,id", "team:id-", ":id", "authority:id+"
}, unique = {
"username"
})
public class UserModel {
public enum Role {
PROGRAMMER, DESIGNNER, MANAGER
}
public enum Authority {
USER, ADMIN
}
@DataModelAttrs(primaryKey = true)
private Long id;
private String username;
private String nickname;
private String team;
private Role role;
private Date createdAt;
private List<String> tags;
/* GetterとSetterは省略 */
}
この状態でビルドを実行すると、APTが実行され次のようなSQLiteDatabaseを使うのに便利なクラスが自動生成されます。
自動生成されるクラスの名前は<モデルクラス名>+CatHandsです。
@DataModelのfind属性で指定した組み合わせが、unique属性のものを全て含む場合は戻り値が単一取得に、
含まない場合は複数取得として戻り値がListになるなど、アプリ作成が楽になるようにも工夫されています。
public class UserModelCatHands {
public static final IAccessor<Long> ACCESSOR_ID;
public static final IAccessor<String> ACCESSOR_USERNAME;
public static final IAccessor<String> ACCESSOR_NICKNAME;
public static final IAccessor<String> ACCESSOR_TEAM;
public static final IAccessor<net.cattaka.util.cathandsgendroid.test.model.UserModel.Role> ACCESSOR_ROLE;
public static final IAccessor<java.util.Date> ACCESSOR_CREATED_AT;
public static final IAccessor<java.util.List<String>> ACCESSOR_TAGS;
public static final IAccessor<net.cattaka.util.cathandsgendroid.test.model.UserModel.Authority> ACCESSOR_AUTHORITY;
public static final IAccessor<byte[]> ACCESSOR_BLOB;
public static final IAccessor<Boolean> ACCESSOR_BOOLEAN_DATA;
public static final IAccessor<Byte> ACCESSOR_BYTE_DATA;
public static final IAccessor<Character> ACCESSOR_CHAR_DATA;
static {
ACCESSOR_ID = Accessors.LongAccessor.createAccessor(Long.class);
ACCESSOR_USERNAME = Accessors.StringAccessor.createAccessor(String.class);
ACCESSOR_NICKNAME = Accessors.StringAccessor.createAccessor(String.class);
ACCESSOR_TEAM = Accessors.StringAccessor.createAccessor(String.class);
ACCESSOR_ROLE = net.cattaka.util.cathandsgendroid.accessor.EnumNameAccessor
.createAccessor(net.cattaka.util.cathandsgendroid.test.model.UserModel.Role.class);
ACCESSOR_CREATED_AT = Accessors.DateAccessor.createAccessor(java.util.Date.class);
ACCESSOR_TAGS = Accessors.createListAccessor(Accessors.StringAccessor
.createAccessor(String.class));
ACCESSOR_AUTHORITY = net.cattaka.util.cathandsgendroid.accessor.EnumOrderAccessor
.createAccessor(net.cattaka.util.cathandsgendroid.test.model.UserModel.Authority.class);
ACCESSOR_BLOB = Accessors.BlobAccessor.createAccessor(byte[].class);
ACCESSOR_BOOLEAN_DATA = Accessors.BooleanAccessor.createAccessor(Boolean.class);
ACCESSOR_BYTE_DATA = Accessors.ByteAccessor.createAccessor(Byte.class);
ACCESSOR_CHAR_DATA = Accessors.CharacterAccessor.createAccessor(Character.class);
}
public static final String SQL_CREATE_TABLE = "CREATE TABLE userModel(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,nickname TEXT,team TEXT,role TEXT,createdAt INTEGER,tags BLOB,authority INTEGER,blob BLOB,booleanData INTEGER,byteData INTEGER,charData INTEGER)";
public static final String[] SQL_ALTER_TABLE_1_TO_2 = new String[] {
"ALTER TABLE userModel ADD COLUMN nickname TEXT",
"ALTER TABLE userModel ADD COLUMN team TEXT",
};
public static final String[] SQL_ALTER_TABLE_2_TO_3 = new String[] {
"ALTER TABLE userModel ADD COLUMN authority INTEGER",
};
public static void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
for (String sql : SQL_ALTER_TABLE_1_TO_2) {
db.execSQL(sql);
}
}
if (oldVersion < 3) {
for (String sql : SQL_ALTER_TABLE_2_TO_3) {
db.execSQL(sql);
}
}
};
public static final String TABLE_NAME = "userModel";
public static final String COLUMNS = "id,username,nickname,team,role,createdAt,tags,authority,blob,booleanData,byteData,charData";
public static final String[] COLUMNS_ARRAY = new String[] {
"id", "username", "nickname", "team", "role", "createdAt", "tags", "authority", "blob",
"booleanData", "byteData", "charData"
};
public static final int COL_INDEX_ID = 0;
public static final int COL_INDEX_USERNAME = 1;
public static final int COL_INDEX_NICKNAME = 2;
public static final int COL_INDEX_TEAM = 3;
public static final int COL_INDEX_ROLE = 4;
public static final int COL_INDEX_CREATED_AT = 5;
public static final int COL_INDEX_TAGS = 6;
public static final int COL_INDEX_AUTHORITY = 7;
public static final int COL_INDEX_BLOB = 8;
public static final int COL_INDEX_BOOLEAN_DATA = 9;
public static final int COL_INDEX_BYTE_DATA = 10;
public static final int COL_INDEX_CHAR_DATA = 11;
public static final String COL_NAME_ID = "id";
public static final String COL_NAME_USERNAME = "username";
public static final String COL_NAME_NICKNAME = "nickname";
public static final String COL_NAME_TEAM = "team";
public static final String COL_NAME_ROLE = "role";
public static final String COL_NAME_CREATED_AT = "createdAt";
public static final String COL_NAME_TAGS = "tags";
public static final String COL_NAME_AUTHORITY = "authority";
public static final String COL_NAME_BLOB = "blob";
public static final String COL_NAME_BOOLEAN_DATA = "booleanData";
public static final String COL_NAME_BYTE_DATA = "byteData";
public static final String COL_NAME_CHAR_DATA = "charData";
public static void readCursorByIndex(Cursor cursor, UserModel dest) {
dest.setId(ACCESSOR_ID.readFromCursor(cursor, 0));
dest.setUsername(ACCESSOR_USERNAME.readFromCursor(cursor, 1));
dest.setNickname(ACCESSOR_NICKNAME.readFromCursor(cursor, 2));
dest.setTeam(ACCESSOR_TEAM.readFromCursor(cursor, 3));
dest.setRole(ACCESSOR_ROLE.readFromCursor(cursor, 4));
dest.setCreatedAt(ACCESSOR_CREATED_AT.readFromCursor(cursor, 5));
dest.setTags(ACCESSOR_TAGS.readFromCursor(cursor, 6));
dest.setAuthority(ACCESSOR_AUTHORITY.readFromCursor(cursor, 7));
dest.setBlob(ACCESSOR_BLOB.readFromCursor(cursor, 8));
dest.setBooleanData(ACCESSOR_BOOLEAN_DATA.readFromCursor(cursor, 9));
dest.setByteData(ACCESSOR_BYTE_DATA.readFromCursor(cursor, 10));
dest.setCharData(ACCESSOR_CHAR_DATA.readFromCursor(cursor, 11));
}
public static UserModel readCursorByIndex(Cursor cursor) {
UserModel result = new UserModel();
readCursorByIndex(cursor, result);
return result;
}
public static void readCursorByName(Cursor cursor, UserModel dest) {
int idx;
idx = cursor.getColumnIndex("id");
dest.setId(ACCESSOR_ID.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("username");
dest.setUsername(ACCESSOR_USERNAME.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("nickname");
dest.setNickname(ACCESSOR_NICKNAME.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("team");
dest.setTeam(ACCESSOR_TEAM.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("role");
dest.setRole(ACCESSOR_ROLE.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("createdAt");
dest.setCreatedAt(ACCESSOR_CREATED_AT.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("tags");
dest.setTags(ACCESSOR_TAGS.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("authority");
dest.setAuthority(ACCESSOR_AUTHORITY.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("blob");
dest.setBlob(ACCESSOR_BLOB.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("booleanData");
dest.setBooleanData(ACCESSOR_BOOLEAN_DATA.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("byteData");
dest.setByteData(ACCESSOR_BYTE_DATA.readFromCursor(cursor, idx));
idx = cursor.getColumnIndex("charData");
dest.setCharData(ACCESSOR_CHAR_DATA.readFromCursor(cursor, idx));
}
public static UserModel readCursorByName(Cursor cursor) {
UserModel result = new UserModel();
readCursorByName(cursor, result);
return result;
}
public static void putToContentValues(ContentValues dest, UserModel model,
boolean withPrimaryKey) {
if (withPrimaryKey) {
ACCESSOR_ID.putToContentValues(dest, "id", model.getId());
}
ACCESSOR_USERNAME.putToContentValues(dest, "username", model.getUsername());
ACCESSOR_NICKNAME.putToContentValues(dest, "nickname", model.getNickname());
ACCESSOR_TEAM.putToContentValues(dest, "team", model.getTeam());
ACCESSOR_ROLE.putToContentValues(dest, "role", model.getRole());
ACCESSOR_CREATED_AT.putToContentValues(dest, "createdAt", model.getCreatedAt());
ACCESSOR_TAGS.putToContentValues(dest, "tags", model.getTags());
ACCESSOR_AUTHORITY.putToContentValues(dest, "authority", model.getAuthority());
ACCESSOR_BLOB.putToContentValues(dest, "blob", model.getBlob());
ACCESSOR_BOOLEAN_DATA.putToContentValues(dest, "booleanData", model.getBooleanData());
ACCESSOR_BYTE_DATA.putToContentValues(dest, "byteData", model.getByteData());
ACCESSOR_CHAR_DATA.putToContentValues(dest, "charData", model.getCharData());
}
public static long insert(SQLiteDatabase db, UserModel model) {
ContentValues values = new ContentValues();
putToContentValues(values, model, false);
long key = db.insert(TABLE_NAME, null, values);
model.setId((long)key);
return key;
}
public static int update(SQLiteDatabase db, UserModel model) {
ContentValues values = new ContentValues();
String whereClause = "id=?";
String[] whereArgs = new String[] {
ACCESSOR_ID.stringValue(model.getId())
};
putToContentValues(values, model, false);
return db.update(TABLE_NAME, values, whereClause, whereArgs);
}
public static int delete(SQLiteDatabase db, Long key) {
String whereClause = "id=?";
String[] whereArgs = new String[] {
ACCESSOR_ID.stringValue(key)
};
return db.delete(TABLE_NAME, whereClause, whereArgs);
}
public static UserModel findById(SQLiteDatabase db, Long id) {
Cursor cursor = findCursorById(db, id);
UserModel model = (cursor.moveToNext()) ? readCursorByIndex(cursor) : null;
cursor.close();
return model;
}
public static Cursor findCursorById(SQLiteDatabase db, Long id) {
String selection = "id=?";
String[] selectionArgs = new String[] {
ACCESSOR_ID.stringValue(id),
};
return db.query(TABLE_NAME, COLUMNS_ARRAY, selection, selectionArgs, null, null, null);
}
public static UserModel findByUsername(SQLiteDatabase db, String username) {
Cursor cursor = findCursorByUsername(db, username);
UserModel model = (cursor.moveToNext()) ? readCursorByIndex(cursor) : null;
cursor.close();
return model;
}
public static Cursor findCursorByUsername(SQLiteDatabase db, String username) {
String selection = "username=?";
String[] selectionArgs = new String[] {
ACCESSOR_USERNAME.stringValue(username),
};
return db.query(TABLE_NAME, COLUMNS_ARRAY, selection, selectionArgs, null, null, null);
}
public static java.util.List<UserModel> findByTeamOrderByRoleAscAndIdAsc(SQLiteDatabase db,
int limit, String team) {
Cursor cursor = findCursorByTeamOrderByRoleAscAndIdAsc(db, limit, team);
java.util.List<UserModel> result = new java.util.ArrayList<UserModel>();
while (cursor.moveToNext()) {
result.add(readCursorByIndex(cursor));
}
cursor.close();
return result;
}
public static Cursor findCursorByTeamOrderByRoleAscAndIdAsc(SQLiteDatabase db, int limit,
String team) {
String selection = "team=?";
String[] selectionArgs = new String[] {
ACCESSOR_TEAM.stringValue(team),
};
String limitStr = (limit > 0) ? String.valueOf(limit) : null;
String orderBy = "role asc,id asc";
return db.query(TABLE_NAME, COLUMNS_ARRAY, selection, selectionArgs, null, null, orderBy,
limitStr);
}
public static java.util.List<UserModel> findByTeamOrderByIdDesc(SQLiteDatabase db, int limit,
String team) {
Cursor cursor = findCursorByTeamOrderByIdDesc(db, limit, team);
java.util.List<UserModel> result = new java.util.ArrayList<UserModel>();
while (cursor.moveToNext()) {
result.add(readCursorByIndex(cursor));
}
cursor.close();
return result;
}
public static Cursor findCursorByTeamOrderByIdDesc(SQLiteDatabase db, int limit, String team) {
String selection = "team=?";
String[] selectionArgs = new String[] {
ACCESSOR_TEAM.stringValue(team),
};
String limitStr = (limit > 0) ? String.valueOf(limit) : null;
String orderBy = "id desc";
return db.query(TABLE_NAME, COLUMNS_ARRAY, selection, selectionArgs, null, null, orderBy,
limitStr);
}
public static java.util.List<UserModel> findOrderByIdAsc(SQLiteDatabase db, int limit) {
Cursor cursor = findCursorOrderByIdAsc(db, limit);
java.util.List<UserModel> result = new java.util.ArrayList<UserModel>();
while (cursor.moveToNext()) {
result.add(readCursorByIndex(cursor));
}
cursor.close();
return result;
}
public static Cursor findCursorOrderByIdAsc(SQLiteDatabase db, int limit) {
String selection = "";
String[] selectionArgs = new String[] {
};
String limitStr = (limit > 0) ? String.valueOf(limit) : null;
String orderBy = "id asc";
return db.query(TABLE_NAME, COLUMNS_ARRAY, selection, selectionArgs, null, null, orderBy,
limitStr);
}
public static java.util.List<UserModel> findByAuthorityOrderByIdAsc(SQLiteDatabase db,
int limit, net.cattaka.util.cathandsgendroid.test.model.UserModel.Authority authority) {
Cursor cursor = findCursorByAuthorityOrderByIdAsc(db, limit, authority);
java.util.List<UserModel> result = new java.util.ArrayList<UserModel>();
while (cursor.moveToNext()) {
result.add(readCursorByIndex(cursor));
}
cursor.close();
return result;
}
public static Cursor findCursorByAuthorityOrderByIdAsc(SQLiteDatabase db, int limit,
net.cattaka.util.cathandsgendroid.test.model.UserModel.Authority authority) {
String selection = "authority=?";
String[] selectionArgs = new String[] {
ACCESSOR_AUTHORITY.stringValue(authority),
};
String limitStr = (limit > 0) ? String.valueOf(limit) : null;
String orderBy = "id asc";
return db.query(TABLE_NAME, COLUMNS_ARRAY, selection, selectionArgs, null, null, orderBy,
limitStr);
}
}
使い方:生成されたコードの使い方
使うときは簡単にそれぞれの場合で以下のように呼び出せば使用できます。
// Uniqueな項目で検索する場合、戻り値はUserModelになります。
UserModel model = UserModelCatHands.findByUsername(db, "taro");
// 非Uniqueな項目で検索する場合、戻り値はList<UserModel>になります。
List<UserModel> models = UserModelCatHands.findByTeamOrderByRoleAscAndIdAsc(db, 0, "A");
// INSERTを実行する場合、引数に追加したいモデルを与える。
UserModelCatHands.insert(db, model);
// UPDATEを実行する場合、引数に更新したいモデルを与える。
UserModelCatHands.update(db, model);
// DELETEを実行する場合、引数に削除したいモデルのidを与える。
UserModelCatHands.delete(db, model.getId());
まとめ
いかがだったでしょうか。
今回紹介した機能はごく一部で、Cursorを取得するものや、
SQLからモデルクラスにマッピングする機能など、機能は他にもまだまだあります。
上の自動生成されたコードのメソッドを一つずつ見ていただければ他にも理解が早いかも知れません。
またAPTで自動生成されたコードなので、もし細かな仕様が沿わないとき、
これらのコードをコピー&ペーストして必要なところだけ書き換えて対応することができるのも利点です。
次回以降は他にもある機能についてご紹介したいと思います。