SSブログ

Androidアプリ開発 SQLiteデータベースを使用する(トランザクション) SQLポケリ [Androidアプリ開発]

前回の記事
SQLite Androidアプリ開発 SQLiteデータベースを使用する

SQLiteは小さいデータベースシステムではあるが、ちゃんとトランザクションをサポートしている。トランザクションをかけておけば、エラーになったときにまとめて「なかったこと」にできるので便利。
また、大量のデータをINSERTする場合は、トランザクションをかけた方が高速。

普通のデータベースでは、BEGIN TRANSACTIONによりトランザクションを開始して、INSERTやDELETEでレコードを追加、削除する。処理に成功したら、COMMIT TRANSACTIONでコミットしてトランザクションを閉じる。途中でエラーになったら、ROLLBACK TRANSACTIONでトランザクションをかける前の状態に戻す。

Javaのコードで書くと以下のような感じになる。

	db.execSQL("BEGIN TRANSACTION");	// トランザクション開始
	try {
		processData();			// データを追加したり削除したり
		success = true;
	}
	catch(Exception e){			// 例外発生
		e.printStackTrace();
		success = false;
	}
	if ( success ){
		db.execSQL("COMMIT TRANSACTION");	// コミット
	}
	else {
		db.execSQL("ROLLBACK TRANSACTION");	// ロールバック
	}


AndroidのSQLiteDatabaseクラスにもトランザクションを制御するメソッドが備わっているが、少々使い方が異なる。

SQLiteDatabase#beginTransactionメソッドを呼び出すことでトランザクションが開始される。SQL命令でいうところのBEGIN TRANSACTIONを実行したことに等しい。

レコードの追加や削除が終了し、それを確定させたい場合、SQLiteDatabase#setTransactionSuccessfulメソッドを呼び出す。これは、SQL命令のCOMMIT TRANSACTIONと同じではない。まだ確定はしていない。上記の例でsuccessのフラグを立てた状態と同じ。

beginTransactionで開始したトランザクションは、endTransactionで終了させる。successフラグが立っていれば(setTransactionSuccessfulが呼び出されていれば)、COMMIT TRANSACTIONと同じ。successフラグが立っていなければ、ROLLBACK TRANSACTIONと同じになる。

つまりですね。BEGIN TRANSACTIONに対応するメソッドはあるが、COMMIT TRANSACTIONとROLLBACK TRANSACTIONに対応するメソッドがないっていうこと。
トランザクションは、endTransactionで終了させるが、そのときにCOMMITされるかROLLBACKされるかは、setTransactionSuccessfullが呼び出されたかどうかで決定される。

えーと、こんな説明で分かって頂けたでしょうか。Javaのコードにしたらもっと簡単です。

	db.beginTransaction();			// トランザクション開始
	try {
		processData();			// データを追加したり削除したり
		db.setTransactionSuccessful();	// 成功
	}
	catch(Exception e){			// 例外発生
		e.printStackTrace();
	}
	finally{
		db.endTransaction();		// トランザクション終了
	}


processData内で例外が発生すると、setTransactionSuccessfulは実行されないので、endTransactionではロールバックされる。
例外が発生せず、setTransactionSuccessfulが呼び出されれば、endTransactionでコミットされる。

ロック

トランザクションとくれば、次はロックでしょう。Androidはマルチタスクやスレッドが可能なので、データの更新が競合する可能性がある。
SQLiteでのロックの粒度は、データベース単位。Oracleなどでは行単位のロックが可能。SQLiteは小さい組み込み用のデータベースなので、ロックの粒度はおおざっぱでかまわないのであろう。

どういうことかというと、あるコードでデータベースを更新している間は、他のコードからはデータベースを参照することができないようにロックがかかる。Oracleなどの高機能なデータベースは、行単位などの細かい粒度でロックされるが、SQLiteの場合は、データベース単位でロックされる。ということ。

テーブル単位ではなく、データベース単位。

どういうことかというと、どこかのスレッドでトランザクションを開始したら、そのデータベースは、そのスレッドがロック保持者になる。別のスレッドからデータベースにアクセスできない状態になる。
別のテーブルに書き込むからOKでしょ。というわけにはいかない。

トランザクションのネスト

トランザクションをネストさせる場合を考えてみて欲しい。ネストというのは、入れ子にすること。トランザクションの中でまたトランザクションをかけるのである。

	db.beginTransaction();			// トランザクション開始
	try {
		db.beginTransaction();		// 子トランザクション1開始
		try {
			db.execSQL("INSERT INTO sample VALUES('transaction1')");
			db.setTransactionSuccessful();	// 成功
		}
		finally {
			db.endTransaction();	// 子トランザクション1終了
		}
		db.beginTransaction();		// 子トランザクション2開始
		try {
			db.execSQL("INSERT INTO sample VALUES('transaction2')");
		}
		finally {
			db.endTransaction();	// 子トランザクション2終了
		}
		db.setTransactionSuccessful();	// 成功
	}
	finally {
		db.endTransaction();		// トランザクション終了
	}


transaction1の値を持つレコードはINSERTで作成されるが、transaction2のレコードは追加されない。
もし、外側のトランザクションがロールバックされれば、transaction1のレコードも追加されない。

トランザクションの開始、コミット、ロールバックをリスナーで受ける

トランザクションをbeginTransactionWithListenerで開始するとその状態変化をリスナーで受け取ることができる。ちょとやってみる。

public class TestSQLiteActivity extends Activity implements SQLiteTransactionListener {
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this);
		SQLiteDatabase db = helper.getWritableDatabase();
		db.beginTransactionWithListener(this);
		try {
			db.execSQL("INSERT INTO sample(name) VALUES('test data')");
			db.setTransactionSuccessful();
		}
		catch(Exception e){			// 例外発生
			e.printStackTrace();
		}
		finally {
			db.endTransaction();
		}
		Cursor c = db.rawQuery("SELECT * FROM sample", null);
		boolean hasRecord = c.moveToFirst();
		while(hasRecord) {
			android.util.Log.v("database sample", c.getInt(0) + "," + c.getString(1));
			hasRecord = c.moveToNext();
		}
		c.close();
		db.close();
	}

	@Override
	public void onBegin() {
		android.util.Log.i("TestSQLite", "transaction begin");
	}

	@Override
	public void onCommit() {
		android.util.Log.i("TestSQLite", "transaction commit");
	}

	@Override
	public void onRollback() {
		android.util.Log.i("TestSQLite", "transaction rollback");
	}
}


SQLiteTransactionListenerインターフェースを実装したクラスで通知を受けることができる。ロールバックした際にエラーを表示する、といった用途に使用できそう。

お約束の宣伝を... SQLについての詳細は、手前味噌で恐縮ではあるが「SQLポケットリファレンス」を参照されたし。
「改訂第4版SQLポケットリファレンス」は、SQLiteにも対応しています。




サイト内を検索

タグ:SQLite Android SQL
nice!(0)  コメント(0) 
共通テーマ:携帯コンテンツ

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。



Copyright Atsushi Asai Google+朝井淳
[改訂第4版]SQLポケットリファレンス

[改訂第4版]SQLポケットリファレンス

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/02/18
  • メディア: 単行本(ソフトカバー)

イラストで理解 SQL はじめて入門

イラストで理解 SQL はじめて入門

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2019/05/16
  • メディア: 単行本(ソフトカバー)

[データベースの気持ちがわかる]SQLはじめの一歩 (WEB+DB PRESS plus)

[データベースの気持ちがわかる]SQLはじめの一歩 (WEB+DB PRESS plus)

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2015/03/03
  • メディア: 単行本(ソフトカバー)

Access クエリ 徹底活用ガイド ~仕事の現場で即使える

Access クエリ 徹底活用ガイド ~仕事の現場で即使える

  • 作者: 朝井 淳
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/05/25
  • メディア: 大型本

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。