Android開発録 トランザクションの扱い
バルクインサートを導入した
理由としては、1000曲近い曲データ(×難易度分)を1行入れてDBコミット、1行入れてはコミット…ということをしていたらくっそ遅かったから。
それを改善するために、
トランザクションを張る→その間にインサートをする→最後にコミット
をする、いわゆる「バルクインサート」を導入した。
なんかインサートしなくなったぞ?
バルクインサートコードをほぼ書き込み、いざテストだ!と踏み込んだ結果、データがインサートされない。
理由を探ってみる…
※下のコードを見た瞬間わかった人はちょっと座っててください
SQLiteStatement stmt = db.compileStatement(sql.toString()); db.beginTransaction(); for(ContentValues val :values){ int i=0; for(String col:val.keySet()){ i++; stmt.bindString(i,val.getAsString(col)); } returnRow = stmt.executeInsert(); } db.setTransactionSuccessful();
んー、ぱっと見問題は見えない…
SQLが間違っていたらPreparedStatement作成時に落ちるだろうし、バインドも問題なく行ってるっぽい。
んでもって、トランザクションへの成功宣言をしておしまい…ん?
成功宣言ってなんだ?
ここで出てくる成功宣言とは、
db.setTransactionSuccessful();
のこと。
これを入れることでトランザクションの成功を宣言する…
でも、「成功を宣言する」だけであって、トランザクション自体が終わってない…?
ドキュメントを再度確認…
db.endTransaction();
こいつね、こいつが必要だったんね…。
ではsetTransactionSuccessfulとは何者か?
ソースコードを掘って確認してみよう。
下記のコードはSQLiteSession.javaの1ソースである。
public void setTransactionSuccessful() { throwIfNoTransaction(); throwIfTransactionMarkedSuccessful(); mTransactionStack.mMarkedSuccessful = true; }
トランザクション成否のフラグをいじってるだけなんね…
明示的にフラグをいじるのはこのメソッドしかないようだ。
ロールバックとかどうすりゃいいんだこれ…要調査かな?
結果、こうなる
try{ for(ContentValues val :values){ int i=0; for(String col:val.keySet()){ i++; stmt.bindString(i,val.getAsString(col)); } returnRow = stmt.executeInsert(); } db.setTransactionSuccessful(); } finally { db.endTransaction(); }
try-finallyで囲んであるのは、最終処理でトランザクションを閉じることを明示的に表すため。
普通に書く分だったら、特に例外を吐かないメソッドたちなのでウォーターフォール式に書いても問題ない…と思う。
なんでどのレコードもインサートされてなかったのか?
トランザクションを張る→データインサートを順次行う→トランザクションは張られたまま
コレが起きていたため、トランザクションセッション内ではインサートが行われているのだが
外部セッション(目に見えるデータ)に関してはインサートが完了していなかった状態になっていた。
回りくどい言い方をしたが、要するに「トランザクションを閉じる」≒「コミットを行う」という状態。
感じたこと
せっかくSQL界隈には「commit」や「rollback」というありがたい言葉があるんだから、そっちを使ってくれませんかね…
この問題で一番感じたのはsetTransactionSuccessfulというメソッド名だとcommitも行っていると俺のように勘違いする人がいるんじゃないかなーって…。
余計な話
たまにソースのコピペではなく、自分でコードを読み解いて、実装して、躓いて、解決して、自分の解釈や考えを出してみるとけっこう面白いもんですね…。