iPhoneでSQLite3を使う2(DBファイルのオープンとトランザクションとは)

前回、twitterDB.sqliteというDBファイルを作成するところまでやりました。今回は、プログラムからテーブルを使える状態にする準備部分の実装をやります。


まず、前回作った「twitterDB.sqlite」を、xcodeプロジェクトに追加してください。
それと、#import の追加。そして、Frameworkにlibsqlite3.dylibを追加します。下記は、xCode4での追加イメージです。Link binary with librariesの+ボタンを押して、libsqlite3.dylibを選択します。




次に接続情報の取得です。プログラムからSQLを使って、テーブルに対するデータの追加や、中身を検索するためには、DBファイルを開くという操作が必要です。開くことでDBとの接続情報が取得でき、以降はこの接続情報をもとにsqlite3に操作の要求を投げることができるようになります。


DBを利用するプログラムに、sqlite3の接続を保持する変数を定義します。

sqlite3* db_;

接続情報を取得するためのコードです。DBファイルのパスを取得して、sqlite3_openで接続情報を取得しています。※エラー処理部分は、とりあえず保留。

-(void)dbOpen {
	NSLog(@"DB Open");

	NSString* database_filename;
	NSString* template_path;
	NSString* database_path;
	NSString* work_path;

	// データベース名
	database_filename = @"twitterDB.sqlite";

	// データベースファイルを格納するために文書フォルダーを取得します。
	work_path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

	// データベースファイルのパスを取得します。
	database_path = [NSString stringWithFormat:@"%@/%@", work_path, database_filename];

	// 文書フォルダーにデータベースファイルが存在しているかを確認します。
	NSFileManager* manager = [NSFileManager defaultManager];

	if (![manager fileExistsAtPath:database_path])
	{
		NSError* error = nil;

		// 文書フォルダーに存在しない場合は、データベースの複製元をバンドルから取得します。
		template_path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:database_filename];

		// バンドルから取得したデータベースファイルを文書フォルダーにコピーします。
		if (![manager copyItemAtPath:template_path toPath:database_path error:&error])
		{
			// データベースファイルのコピーに失敗した場合の処理です。
			// 保留
		}
	}

	// 文書フォルダーに用意されたデータベースファイルを開きます。
	if (sqlite3_open([database_path UTF8String], &db_) != SQLITE_OK)
	{
		// データベースファイルを SQLite で開くことに失敗しました。
		// 保留
	}
}

DBを使い終わったら閉じる、sqlite3_closeで接続情報を解放します。

-(void)dbClose {
	NSLog(@"DB Close");

	if (db_) {
		// DB Close
		sqlite3_close(db_);
	}
	db_ = nil;
}

オープンして、テーブル操作、操作が終わったらクローズという形で利用します。


次はSQLによるテーブルデータの操作といきたいところですが、その前にトランザクションについて書きたいと思います。
トランザクションって何やねん?」と思われる方もおられると思いますが、トランザクションとは次のような場合に必要となります。

  • 複数のテーブルを使う場合。複数のテーブルを同時に更新して、両方とも成功しないとつじつまが合わない場合。

  ex)銀行テーブルからお金を引いて、財布テーブルにお金を足すような場合。引いて、お金を足す方で失敗した場合、つじつまが合わなくなって困りますよね。

  • まとめて更新したい場合。複数のデータを操作する場合で、途中で失敗した場合は、全てをキャンセルにしたい場合。

  ex)商品を3つ買って注文テーブルに、注文データを追加する場合。2つ成功して、3つ目で失敗したら、成功分だけ残ったら困る場合がありますよね。
このような場合に、DBでは追加や更新したデータは一時的な領域に保存し、任意のタイミングでまとめて書き込む仕組みがあります。


使い方は
1.トランザクションの開始、「ここからまとめて更新して」というBEGIN文を発行
2.トランザクションの終了
 ・すべての処理が成功したら「まとめて書き込む」というCOMMIT文を発行
 ・処理が失敗したら「まとめてキャンセル」というROLLBACK文を発行
という流れです。


それらをコードで書くと、次のようになります。sqlite3_execで、各コマンドを発行するだけです。

-(void)dbBegin {
	sqlite3_exec(db_, "BEGIN", NULL, NULL, NULL );
}

-(void)dbCommit {
	sqlite3_exec(db_, "COMMIT", NULL, NULL, NULL );
}

-(void)dbRollback {
	sqlite3_exec(db_, "ROLLBACK", NULL, NULL, NULL );
}



ちょっと骨格を作ってみましょう。オープンからクローズの流れは

// DB接続
[self dbOpen];

// トランザクションの開始
[self dbBegin];

// 処理
 ・
 ・
 ・
if (処理成功?) {
	// トランザクションの終了 コミット(まとめて書き込む)
	[self dbCommit];
} else {
	// トランザクションの終了 ロールバック(まとめてキャンセル)
	[self dbRollback];
}

// DB切断
[self dbClose];

というような感じです。今日はここまでw

※SQLite3でDBに追加、更新する場合は、トランザクションの開始と終了をしないとめちゃくちゃ遅くなるらしいです。