[Android] データの取り扱い - データベース(SQLite) ================================================================================ データベース(SQLite)を使って、データの照会・保存・変更・削除を行う。 データ(列)は、つぎの 2 つ。 ・名前 ・コメント なお、データベースのテーブルを使うにあたり、このデータを区別するための主キーとし て、ID 列をつける。なお、SQLite では、この「PRIMARY KEY AUTOINCREMENT」の属性を 持った列は、「_id」という列名にする。こうしておくことで、CursorAdapter クラスを 使って ListView にデータを表示させることができる。 ※ただし、CursorAdapter を使うのは、V3.x 以降では推奨されていないようだ。 データベースのテーブル生成文は以下のとおり。 CREATE TABLE item ( _id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT , comment TEXT ) このサンプル・アプリケーションではつぎのことができる。 ・照会 「名前」欄に記述されたものと一致するものをすべて取得するが、表示するのは最初 に取得したものだけ。 ・保存 「名前」「コメント」欄に記述された内容をデータベースのテーブルに保存する。 ・変更 「名前」欄に記述されたものと一致するものすべての「コメント」を、すべて変更す る。 ・削除 「名前」欄に記述されたものと一致するものすべてを削除する。 データベースを扱うクラスとして、DatabaseHelper クラスを作る。これは、 SQLiteOpenHelper クラス(抽象クラス)を継承して作る。 次のメソッドのオーバーライドが必要。 abstract void onCreate(SQLiteDatabase db) abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 今回のクラス --- Database01Activity アクティビティ DatabaseHelper データベース操作 Item エンティティ --- ▲ アクティビティ データベースに関する操作はすべて DatabaseHelper オブジェクトで行っている。 □ Database01Activity.java --- package jp.marunomaruno.android.database; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.Toast; public class Database01Activity extends Activity { private EditText name = null; private EditText comment = null; private DatabaseHelper dbHelper = null; // (1) private int TOAST_DURATION = Toast.LENGTH_SHORT; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name = (EditText) findViewById(R.id.name); comment = (EditText) findViewById(R.id.comment); dbHelper = new DatabaseHelper(this); // (2) } @Override public void onPause() { super.onPause(); if (dbHelper != null) { // dbHelper.close(); // 呼ばないほうがよい // (3) } } public void onFindButtonClick(View view) { List<Item> itemList = dbHelper.select(name.getText()); // (4) if (itemList.size() <= 0) { Toast.makeText(this, getString(R.string.notFoundMessage), TOAST_DURATION).show(); return; } name.setText(itemList.get(0).getName()); comment.setText(itemList.get(0).getComment()); Toast.makeText( this, (itemList.size() == 1 ? String .format(getString(R.string.findMessage)) : String .format(getString(R.string.findMultipleMessage), itemList.size())), TOAST_DURATION).show(); } public void onSaveButtonClick(View view) { long id = dbHelper.insert(name.getText(), comment.getText()); // (5) name.setText(""); comment.setText(""); if (id > 0) { Toast.makeText(this, String.format(getString(R.string.saveMessage), id), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notSaveMessage), TOAST_DURATION).show(); } } public void onUpdateButtonClick(View view) { int count = dbHelper.update(name.getText(), comment.getText()); // (6) if (count > 0) { Toast.makeText(this, String.format(getString(R.string.updateMessage), count), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notUpdateMessage), TOAST_DURATION).show(); } } public void onRemoveButtonClick(View view) { int count = dbHelper.delete(name.getText()); // (7) name.setText(""); comment.setText(""); if (count > 0) { Toast.makeText(this, String.format(getString(R.string.removeMessage), count), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notRemoveMessage), TOAST_DURATION).show(); } } } --- (1)(2) データベースにアクセスするオブジェクト private DatabaseHelper dbHelper = null; // (1) DatabaseHelper オブジェクトで、データベースを取得しようとしたときに、データベー スがない場合、onCreate() メソッドが動く。 また、データベースのバージョンが変わったときは、onUpgrade() メソッドが動く。 データベースのバージョンは、SQLiteOpenHelper のコンストラクターの引数 int version で指定する。この値が、現在のバージョンが変わったら、このonUpgrade() メソッドが動くことになる。 dbHelper = new DatabaseHelper(this); // (2) (3) データベースを閉じる ただし、これは自分では閉じない方がよいらしい。 // dbHelper.close(); // 呼ばないほうがよい // (3) 参考 「SQLiteDatabase.closeは明示で呼ぶな、Cursor.closeは明示で呼べ」 SQLiteを使う場合の注意点 http://d.hatena.ne.jp/ukiki999/20100524/p1 (4) name で指定されたデータを取得(照会)する 結果は、List オブジェクトで戻る。 List<Item> itemList = dbHelper.select(name.getText()); // (4) (5) name, comment データを挿入する 結果は、主キーの値が戻る。挿入できなかったときは、-1. long id = dbHelper.insert(name.getText(), comment.getText()); // (5) (6) name で指定されたデータを更新する 結果は、更新した行数。 int count = dbHelper.update(name.getText(), comment.getText()); // (6) (7) name で指定されたデータを削除する 結果は、削除した行数。 int count = dbHelper.delete(name.getText()); // (7) ▲ データベース関係 データベースを操作するためのクラス。 SQLiteOpenHelper クラスを継承して、その抽象メソッドであるつぎのメソッドをオー バーライドする。そして、必要に応じて CRUD メソッドを記しておく。 onCreate(SQLiteDatabase db) onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) データベースの CRUD 操作は、getReadableDatabase() メソッド、 getWritableDatabase() メソッドによって、SQLiteDatabase オブジェクトが取得できる ので、このオブジェクトの中のメソッドによる。 □ DatabaseHelper.java --- package jp.marunomaruno.android.database; import java.util.ArrayList; import java.util.List; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.text.Editable; /** * データベースを扱うためのヘルパー・クラス。 * * @author marunomaruno */ public class DatabaseHelper extends SQLiteOpenHelper { // (1) private Context context; private static final String DATABASE_NAME = "item.db"; private static final int DATABASE_VERSION = 1; // (2) public static final String TABLE_NAME = "item"; public static final String ID = "_id"; public static final String NAME = "name"; public static final String COMMENT = "comment"; public static final String[] COLUMN_NAMES = { ID, NAME, COMMENT, }; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); // (3) this.context = context; } @Override public void onCreate(SQLiteDatabase db) { // (4) final String SQL = String.format(context .getString(R.string.createTableFormat), TABLE_NAME, ID, NAME, COMMENT); // (5) System.out.println("DatabaseHelper.onCreate() SQL = " + SQL); db.execSQL(SQL); // (6) } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // (7) db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); // (8) onCreate(db); } /** * 指定されたデータをデータベースに挿入する。 * * @param name * 名前 * @param comment * コメント * @return 主キーの値 */ public long insert(String name, String comment) { String nullColumnHack = null; // NOT NULL列に値が指定されていない場合のデフォルト値 ContentValues values = new ContentValues(); // 挿入する列名と値 // (9) values.put(NAME, name); // (10) values.put(COMMENT, comment); return getWritableDatabase().insert(TABLE_NAME, nullColumnHack, values); // (11) } /** * 指定されたデータをデータベースに挿入する。 * * @param name * 名前 * @param comment * コメント * @return 主キーの値 */ public long insert(Editable name, Editable comment) { return insert(name.toString(), comment.toString()); } /** * 名前で指定されたデータを変更する。 * * @param name * 名前 * @param comment * コメント * @return 変更した行数 */ public int update(String name, String comment) { ContentValues values = new ContentValues(); // 更新する列名と値 values.put(COMMENT, comment); String whereClause = NAME + " = ?"; // 選択条件 // (12) String[] whereArgs = new String[] { name }; // パラメーター・マーカーの値 // (13) return getWritableDatabase().update(TABLE_NAME, values, whereClause, whereArgs); // (14) } /** * 名前で指定されたデータを変更する。 * * @param name * 名前 * @param comment * コメント * @return 変更した行数 */ public int update(Editable name, Editable comment) { return update(name.toString(), comment.toString()); } /** * 名前で指定されたデータを削除する。 * * @param name * 名前 * @return 削除した行数 */ public int delete(String name) { String whereClause = NAME + " = ?"; // 選択条件 String[] whereArgs = new String[] { name }; // パラメーター・マーカーの 値 return getWritableDatabase().delete(TABLE_NAME, whereClause, whereArgs); // (15) } /** * 名前で指定されたデータを削除する。 * * @param name * 名前 * @return 削除した行数 */ public int delete(Editable name) { return delete(name.toString()); } /** * 名前で指定されたデータを照会する。 * * @param name * 名前 * @return 照会したデータのリスト */ public List<Item> select(String name) { String selection = NAME + " = ?"; // 選択条件 String[] selectionArgs = { name }; // パラメーター・マーカーの値 String groupBy = null; // GROUP BY 句 String having = null; // HAVING 句 String orderBy = null; // ORDER BY 句 Cursor cursor = getReadableDatabase().query(TABLE_NAME, COLUMN_NAMES, selection, selectionArgs, groupBy, having, orderBy); // (16) List<Item> itemList = new ArrayList<Item>(); if (!cursor.moveToFirst()) { // 最初の行をさす // (17) cursor.close(); return itemList; } do { itemList.add(new Item(cursor.getLong(cursor.getColumnIndex(ID)), cursor.getString(cursor.getColumnIndex(NAME)), cursor .getString(cursor.getColumnIndex(COMMENT)))); // (18 ) } while (cursor.moveToNext()); // (19) cursor.close(); // (20) return itemList; } /** * 名前で指定されたデータを照会する。 * * @param name * 名前 * @return 照会したデータのリスト */ public List<Item> select(Editable name) { return select(name.toString()); } } --- (1) データベースにアクセスするクラス SQLiteOpenHelper クラスを継承する。 public class DatabaseHelper extends SQLiteOpenHelper { // (1) ・SQLiteOpenHelper クラス データベースと接続するためのクラス。 SQLite では、JDBC と違い、Connection オブジェクトを作るわけではなく、データベー ス自体の管理は、この SQLiteOpenHelper オブジェクトを通して、生成・バージョンアッ プ・接続などを行う。 java.lang.Object - android.database.sqlite.SQLiteOpenHelper ・コンストラクター --- SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) --- name はデータベース名。データベースは、「/data/data/パッケージ名/database/」に構 築される。 factory は、独自カーソルを定義した場合に使う。標準でよければ null。 version は、データベースのバージョン。既存のデータベースのバージョンと違う場合、 旧バージョンとの値の差によって、 onDowngrade() onUpgrade() メソッドが動く。なお、onUpgrade() メソッドは抽象メソッドなので、オーバーライドが 必要であるが、onDowngrade() メソッドは具象メソッドなので、必要なときにオーバーラ イドすればよい。 ・メソッド --- synchronized void close() String getDatabaseName() synchronized SQLiteDatabase getReadableDatabase() synchronized SQLiteDatabase getWritableDatabase() abstract void onCreate(SQLiteDatabase db) void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) void onOpen(SQLiteDatabase db) abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) --- データベースへの接続は、getReadableDatabase() メソッド、getWritableDatabase() メ ソッドで行う。 (2)(3) このデータベースのバージョン 今回は、バージョン 1 にしておく。データベースの構成が変わったときは、2 以上にし て、データベースを作り直す。 private static final int DATABASE_VERSION = 1; // (2) バージョンの指定は、スーパークラス SQLiteOpenHelper のコンストラクターの引数で指 定する。 super(context, DATABASE_NAME, null, DATABASE_VERSION); // (3) (4) データベースがなかったときの処理 データベースに接続するときに、データベースがなかったときにこのメソッドが呼ばれる。 これは、SQLiteOpenHelper クラスの抽象メソッド。 public void onCreate(SQLiteDatabase db) { // (4) (5)(6) データベースとテーブルを生成する CREATE TABLE 文を記している。文の形式はリソースとして定義している。 final String SQL = String.format(context .getString(R.string.createTableFormat), TABLE_NAME, ID, NAME, COMMENT); // (5) 実際の文は以下のとおり。 CREATE TABLE item ( _id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT , comment TEXT ) SQL 文を実行する。 db.execSQL(SQL); // (6) (7) データベースのバージョンがあがったときの処理 これは、SQLiteOpenHelper クラスの抽象メソッド。 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // (7) (8) テーブルがあれば削除して作り直す db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); // (8) onCreate(db); (9)(10) 挿入する列名と値を設定する テーブルに挿入や更新する場合、列名と値のペアで管理する。名前と値のペアというと、 Map オブジェクトがあるが、SQLite では、ContentValues オブジェクトを使う。使い方 は Map と同じで、put() メソッドを使って値を設定。get() メソッドで、値を取得する。 なお、値の型によって、getAsXxx() メソッドもある。 ContentValues values = new ContentValues(); // 挿入する列名と値 // (9) values.put(NAME, name); // (10) □ ContentValues クラス SQLite の DML を使いやすくするのに適した形のマップ(Map)。 ・android.content.ContentValues ・取得・設定関係のメソッド --- Object get(String key) Boolean getAsBoolean(String key) Byte getAsByte(String key) byte[] getAsByteArray(String key) Double getAsDouble(String key) Float getAsFloat(String key) Integer getAsInteger(String key) Long getAsLong(String key) Short getAsShort(String key) String getAsString(String key) void put(String key, Byte value) void put(String key, Integer value) void put(String key, Float value) void put(String key, Short value) void put(String key, byte[] value) void put(String key, String value) void put(String key, Double value) void put(String key, Long value) void put(String key, Boolean value) void putAll(ContentValues other) void putNull(String key) --- ・その他のメソッド --- void clear() boolean containsKey(String key) boolean equals(Object object) int hashCode() Set<String> keySet() void remove(String key) int size() String toString() Set<Entry<String, Object>> valueSet() --- (11) name, comment データを挿入する データを挿入するので、getWritableDatabase() メソッドを使って、書き込みができる モードで、データベースを開く。このメソッドの結果は、SQLiteDatabase オブジェクト。 SQLiteDatabase クラスには、CRUD 系のメソッドが用意されているので、それらを使って テーブルを操作する。 return getWritableDatabase().insert(TABLE_NAME, nullColumnHack, values); // (11) --- long insert(String table, String nullColumnHack, ContentValues values) --- 戻り値は、挿入したときの rowID。INTEGER 型の主キー制約列が定義されていればその値 (のはず)。挿入エラーのときは -1. String table テーブル名 String nullColumnHack NOT NULL 列に値が指定されていない場合のデフォルト値。 ContentValues values 列名と値がマッピングされたオブジェクト (12)(13)(14) name で指定されたデータを更新する 選択条件には、JDBC と同じように、パラメーター・マーカー「?」が使える。 パラメーター・マーカー「?」に対する値は、String 配列値として別に用意する。 String whereClause = NAME + " = ?"; // 選択条件 // (12) String[] whereArgs = new String[] { name }; // パラメーター・マーカーの値 // (13) テーブルの更新なので、getWritableDatabase() メソッドを使う。 return getWritableDatabase().update(TABLE_NAME, values, whereClause, whereArgs); // (14) --- int update(String table, ContentValues values, String whereClause, String[] whereArgs) --- 戻り値は、更新した行数 (15) name で指定されたデータを削除する update() メソッドと同じように、選択条件にはパラメーター・マーカー「?」が使える。 return getWritableDatabase().delete(TABLE_NAME, whereClause, whereArgs); // (15) --- int delete(String table, String whereClause, String[] whereArgs) --- 戻り値は、削除した行数 (16) name で指定されたデータを取得(照会)する ここでは、照会だけなので、読み取り専用で、getReadableDatabase() メソッドを使えば よい。戻り値は Cursor オブジェクト。 Cursor cursor = getReadableDatabase().query(TABLE_NAME, COLUMN_NAMES, selection, selectionArgs, groupBy, having, orderBy); // (16) --- Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) Cursor rawQuery(String sql, String[] selectionArgs) 引数 boolean distinct DISTINCT(true)かALL(false)か String table テーブル名 String[] columns 射影される列名(nullのとき、すべての列) String selection WHERE句(パラメーターマーカー(?)の指定も可能) String[] selectionArgs パラメーターマーカー(?)を置き換える文字列 String groupBy GROUP BY句 String having HAVING句 String orderBy ORDER BY句 String limit LIMIT句 String sql SQL文(パラメーターマーカー含む) --- (17) 取得したテーブルの最初の行をさす カーソルを使って取得したテーブルを処理する。 まずは、最初の行にカーソルを移動する。 if (!cursor.moveToFirst()) { // 最初の行をさす // (17) JDBC と違って、単純に next() メソッドを使うだけでできるわけではないので注意が必 要。moveToFirst() メソッドで、先頭行に移動し、moveToNext() メソッドで、つぎの行 に移動する、というパターン。全体としては、つぎのような感じでロジックを組む。 --- if (!cursor.moveToFirst()) { // 最初の行をさす cursor.close(); return ...; } do { // 行の処理 } while (cursor.moveToNext()); // つぎの行に移動する cursor.close(); --- ・行の移動関係のメソッド --- boolean move(int offset) boolean moveToFirst() boolean moveToLast() boolean moveToNext() boolean moveToPosition(int position) boolean moveToPrevious() --- (18) 行のデータから Item オブジェクトを生成する itemList.add(new Item(cursor.getLong(cursor.getColumnIndex(ID)), cursor.getString(cursor.getColumnIndex(NAME)), cursor .getString(cursor.getColumnIndex(COMMENT)))); // (18) ・ データ取得関係のメソッド --- byte[] getBlob(int columnIndex) double getDouble(int columnIndex) float getFloat(int columnIndex) int getInt(int columnIndex) long getLong(int columnIndex) short getShort(int columnIndex) String getString(int columnIndex) --- 引数としては列番号だけなので、列名から取得したいときは、 getColumnIndex(String columnName) と組み合わせる必要がある。 (19) つぎの行に移動する } while (cursor.moveToNext()); // (19) (20) カーソルを閉じる 使い終わったカーソルは閉じる。 cursor.close(); // (20) 閉じていない場合は、つぎのエラーが出る。 E/Cursor(14725): Finalizing a Cursor that has not been deactivated or closed. ▲エンティティ 単純に、id、name、comment を持っているだけのクラス。 JavaBeans の要件は満たすようにしている。 □ Item.java --- package jp.marunomaruno.android.database; import java.io.Serializable; /** * データベースの項目を管理するエンティティ・クラス。 * * @author marunomaruno */ public class Item implements Serializable { private static final long serialVersionUID = 1L; public static final int NONE_ID = -1; private long id; private String name; private String comment; public Item(long id, String name, String comment) { this.id = id; this.name = name; this.comment = comment; } public Item(String name, String comment) { this(NONE_ID, name, comment); } public Item() { assert true; // 何もしない } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } @Override public String toString() { return String.format("[%d, %s, %s]", id, name, comment); } } --- ▲レイアウト □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:weightSum="1" > <EditText android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/nameHint" > <requestFocus > </requestFocus> </EditText> <EditText android:id="@+id/comment" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/commentHint" android:inputType="textMultiLine" > </EditText> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/findButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onFindButtonClick" android:text="@string/findButton" > </Button> <Button android:id="@+id/saveButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onSaveButtonClick" android:text="@string/saveButton" > </Button> <Button android:id="@+id/updateButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onUpdateButtonClick" android:text="@string/updateButton" > </Button> <Button android:id="@+id/removeButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onRemoveButtonClick" android:text="@string/removeButton" > </Button> </LinearLayout> </LinearLayout> --- ▲ 文字列 --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Database01</string> <string name="nameHint">名前</string> <string name="commentHint">コメント</string> <string name="saveButton">保存</string> <string name="findButton">検索</string> <string name="removeButton">削除</string> <string name="updateButton">変更</string> <string name="saveMessage">ID %d で保存しました</string> <string name="findMessage">データがありました。</string> <string name="findMultipleMessage"> %d 件のデータがありました。最初の1件を表示します</string> <string name="removeMessage">%d 件のデータを削除しました</string> <string name="updateMessage">%d 件のデータを変更しました</string> <string name="notSaveMessage">データを保存できませんでした</string> <string name="notFoundMessage">データはありませんでした</string> <string name="notRemoveMessage">データを削除できませんでした</string> <string name="notUpdateMessage">データを変更できませんでした</string> <string name="createTableFormat"> CREATE TABLE %1$s ( %2$s INTEGER PRIMARY KEY AUTOINCREMENT , %3$s TEXT , %4$s TEXT ) </string> <!-- (1) --> </resources> --- (1) テーブルを生成する SQL 文のフォーマット <string name="createTableFormat"> CREATE TABLE %1$s ( %2$s INTEGER PRIMARY KEY AUTOINCREMENT , %3$s TEXT , %4$s TEXT ) </string> <!-- (1) --> ※ この部分は、国際化とは意味が違うので、別の XML ファイルにした方がよいかもしれ ない。 以上
最新の画像[もっと見る]
-
あけましておめでとうございます 11年前
-
今年もよろしくお願いいたします 12年前
-
あけましておめでとうございます 13年前
-
あけましておめでとうございます 16年前
※コメント投稿者のブログIDはブログ作成者のみに通知されます