Spring BootでCassandraを使っている場合に、トランザクション処理を行う方法を説明します。
トランザクション処理とは
Cassandraに2つのデータを同時に挿入することを考えます。
このとき、エラーが発生した場合でも、片方のデータだけが挿入されている状態は許可したくない、ということがあります。
つまり、1つ目のデータを挿入し、2つ目のデータを挿入するところでエラーが発生した場合、1つ目のデータを削除する、ということです。
これは「トランザクション処理」と呼ばれています。
Spring BootではCassandraBatchOperations
というクラスを使うと、これを実現できます。その方法を説明します。
トランザクション処理の実装方法
ここでは銀行のシステムで、Cassandraにmoneyというテーブルが存在し、以下のようなエンティティクラス(Cassandraのレコードをマッピングするクラス)があるとします。
@Table("money") @Value public class Person { // 名前 @PrimaryKey private String name; // 預金額 private int money; }
このレポジトリクラスは以下のようなコードです。
import org.springframework.data.cassandra.repository.CassandraRepository; public interface MoneyRepository extends CassandraRepository<Money, String> {}
この時、例えば岡田さんが田中さんに500円送金するコードは以下のような感じになります。
CassandraBatchOperations batchOps = cassandraTemplate.batchOps(); // テーブルから情報を取得 Money okada = moneyRepository.findById("岡田"); Money tanaka = moneyRepository.findById("田中"); // 500円送金するので、岡田さんの貯金を500円減らし、田中さんの貯金を500円増やす okada.setMoney(okada.getMoney() - 500); tanaka.setMoney(tanaka.getMoney() + 500); // テーブルの情報を更新 // Cassandraなので、INSERT文を実行すれば、主キーが同じレコードのUPDATEが行われる batchOps.insert(okada); batchOps.insert(tanaka); batchOps.execute();
こうすると、例えば以下のように、1件目のデータの更新と2件目のデータの更新の間で例外を発生させた場合、1件目のデータの更新が取り消されます。
CassandraBatchOperations batchOps = cassandraTemplate.batchOps(); // テーブルから情報を取得 Money okada = moneyRepository.findById("岡田"); Money tanaka = moneyRepository.findById("田中"); // 500円送金するので、岡田さんの貯金を500円減らし、田中さんの貯金を500円増やす okada.setMoney(okada.getMoney() - 500); tanaka.setMoney(tanaka.getMoney() + 500); // テーブルの情報を更新 // Cassandraなので、INSERT文を実行すれば、主キーが同じレコードのUPDATEが行われる batchOps.insert(okada); // ここで例外が発生しても、↑で500円減った岡田さんの貯金は元に戻る throw new RuntimeException("エラーが発生しました。"); batchOps.insert(tanaka); batchOps.execute();
Cassandraでトランザクション処理を行う場面
今回は銀行のシステムを例に取り上げましたが、そもそもの話として、こうしたトランザクション処理をしたい場合には、MySQLなどのRDBを使うべきです。
では、実際に使う場面としてどのような場面があるかというと、以下のようなケースです。
今回は例として、動物園の名前と、そこにいる動物の種類を格納するテーブルを作成するとします。
そして、ある動物園にいる動物の一覧も知りたいし、ある動物がいる動物園の一覧も知りたい、とします。
そうすると、Cassandraは主キー以外での検索ができませんから、テーブルを2つ作成することになります。
以下はそのエンティティクラスです。1つ目のテーブルは動物園名が主キーになっています。
@Table("animal_by_zoo") @Value public class AnimalByZoo { // 動物園の名前 @PrimaryKey private String zoo; // 動物の名前 private String animal; }
2つ目のテーブルは動物名が主キーになっています。
@Table("animal_by_animal") @Value public class AnimalByAnimal { // 動物園の名前 @PrimaryKey private String animal; // 動物の名前 private String zoo; }
このとき、データを追加するには2つのテーブルを同時に更新する必要があります。
CassandraBatchOperations batchOps = cassandraTemplate.batchOps(); batchOps.insert(new AnimalByZoo("上野動物園", "パンダ")); batchOps.insert(new AnimalByAnimal("パンダ", "上野動物園")); batchOps.execute();
こうすると、たとえば上野動物園にいる動物を検索することも、パンダがいる動物園を探すこともできるというわけです。
...ちなみに、こうしたケースでもMySQLなどのRDBを使った方が実装は楽です。また、Cassandraを使う場合でも、セカンダリ・インデックスを使うという手もあります。
ただし、今回説明した方法には処理が高速という利点があり、使いどころがなくもないテクニックなわけです。
もっと詳しく
今回説明したトランザクション処理については、こちらのサイトがよくまとまっています。
複雑なので解読に時間がかかるかもしれませんが、やろうとしていることは、今回の動物園の例と同じです。
つまり、主キーが異なるが格納する情報はほぼ同じ複数のテーブルに対して、データの挿入や削除を行う、といった内容となっています。