この記事の概要
- Cassandraに触れたことがなかったので、構築〜SpringBootのアプリケーションから参照・更新をすることろまでをやってみたときの記録
- 環境はローカルPC(Mac)
参考
Cassandra: http://cassandra.apache.org/doc/latest/
SpringDataCassandra: https://spring.io/projects/spring-data-cassandra
property設定項目: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#data-properties
Cassandraの構築
インストール
# Cassandra本体
brew install cassandra
# CQL(Cassandra Query Language)
brew install cql
起動
cassandra -f
Connection error: ('Unable to connect to any servers', {'127.0.0.1': error(61, "Tried connecting to [('127.0.0.1', 9042)]. Last error: Connection refused")})
動かん。。。
僕の場合はJAVA_HOMEがJava11のディレクトリを指しているのがいけなかったようです。。。
定義し直したら無事起動!
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/
cassandra -f
どうせならHomebrewで起動!
vi /usr/local/etc/cassandra/cassandra-env.sh
# 末尾に「export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/」を追記
brew services start cassandra
接続!
cqlsh
cqlsh localhost 9042
これと一緒
ちょっとCassandra単体で遊んでみる
cqlsh
ここから再開
こまかいオプションの説明はここでは省略。。。
KeySpaceの追加
MySQLやOracleでいうDatabaseに当たる概念
-- # keyspace追加前
DESCRIBE keyspaces
-- system_traces system_schema system_auth system system_distributed
-- # 「sample」 keyspace追加
CREATE KEYSPACE sample WITH REPLICATION = {'class':'SimpleStrategy','replication_factor':1};
-- # keyspace追加前
DESCRIBE keyspaces
-- system_schema system_auth system sample system_distributed system_traces
ちなみに削除は
DROP KEYSPACE sample;
Databaseの移動
USE sample
テーブルの作成
ここではシンプルにKeyとValueを持つデータを想定
-- 作成
CREATE TABLE t_sample (key text PRIMARY KEY, value text);
-- 一覧確認
DESCRIBE tables;
-- テーブル定義の確認
DESCRIBE table t_sample;
ちなみに削除は
DROP TABLE t_sample;
データ操作
先ほど作成したt_sampleテーブルを使って一通り
-- 参照(空っぽ)
select * from t_sample;
-- 参照(上と同等)
select key, value from t_sample;
-- 登録
INSERT INTO t_sample (key, value) VALUES ('key1', 'value1');
-- 登録(重複)
-- クエリは成功して、updateのような動きになっている
INSERT INTO t_sample (key, value) VALUES ('key1', 'value2');
-- 更新
UPDATE t_sample SET value = 'value3' WHERE key = 'key1';
-- 削除
DELETE FROM t_sample WHERE key = 'key1';
primary違反したら弾かれるんじゃなくてupdateされるのは初体験
Springアプリケーションの構築
project作成
plugins {
id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-cassandra'
// webfluxは動作確認用です
implementation 'org.springframework.boot:spring-boot-starter-webflux'
compileOnly('org.projectlombok:lombok')
annotationProcessor('org.projectlombok:lombok')
}
spring:
data:
cassandra:
keyspace-name: sample
テーブルのモデルクラスを作成
import lombok.Value;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
@Data
@Table("t_sample")
public class Sample {
@PrimaryKey
private String key;
private String value;
}
登録・参照・削除をしてみる
@Slf4j
@SpringBootApplication
public class Application {
public static void main(String[] args) {
//SpringApplication.run(Application.class);
Cluster cluster = Cluster.builder()
.addContactPoints("localhost")
.withoutJMXReporting() // 付けないとエラー
.build();
Session session = cluster.connect("sample");
var template = new CassandraTemplate(session);
var data = new Sample("key1", "value1");
template.insert(data);
// 全件検索
var selected = template.select("SELECT * from t_sample", Sample.class);
log.info("selected: {}", selected);
// 更新
data = new Sample("key1", "value2");
template.update(data);
// 条件付き検索
selected = template.select(Query.query(Criteria.where("key").is("key1")), Sample.class);
log.info("selected: {}", selected);
// 削除(PK指定)
template.deleteById("key1", Sample.class);
// 件数取得
var count = template.count(Sample.class);
log.info("count: {}", count);
// 接続クローズ
session.close();
cluster.close();
}
}
.withoutJMXReporting()
については記述しておかないと以下のエラーが発生した
Exception in thread "main" java.lang.NoClassDefFoundError: com/codahale/metrics/JmxReporter
Webアプリケーションっぽくしてみる
まずは「spring-boot-starter-data-cassandra」がどこまでBean登録してくれるのか調査
Cluster
-
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
がBean登録している -
spring.data.cassandra
配下のpropertiesを設定すれば良さそう -
spring.data.cassandra.keyspace-name=sample
とか
Session
- SessionというかSessionFactoryなのかな?
-
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration
がBean登録している - こちらも同様に
spring.data.cassandra
配下の設定が反映されそう
CassandraTemplate
- Session同様、
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration
がBean登録している
結論、、、
いきなりCassandraTemplate使うでOK!
@RequiredArgsConstructor
@RestController
public class CassandraController {
private final CassandraTemplate template;
// curl localhost:8080
@GetMapping("")
public List<Sample> all() {
return template.select("SELECT * FROM t_sample", Sample.class);
}
// curl localhost:8080/key1
@GetMapping("/{key}")
public Sample getByKey(@PathVariable String key) {
return template.selectOneById(key, Sample.class);
}
// curl localhost:8080/insert -d '{"key":"key1","value":"value1"}' -X PATCH -H 'Content-Type: application/json'
@PatchMapping("/**")
public Sample patch(@RequestBody Sample sample) {
return template.insert(sample);
}
// curl localhost:8080/key1 -X DELETE
@DeleteMapping("/{key}")
public Boolean delete(@PathVariable String key) {
return template.deleteById(key, Sample.class);
}
}
Reactive対応
ReactiveCassandraTemplateを使うだけ!
返り値の型をFlux or Monoにするもの忘れずに!(一括でPublisherとしてもOK)
@RequiredArgsConstructor
@RestController
@RequestMapping("/reactive")
public class ReactiveCassandraController {
private final ReactiveCassandraTemplate template;
// curl localhost:8080/reactive
@GetMapping("")
public Flux<Sample> all() {
return template.select("SELECT * FROM t_sample", Sample.class);
}
// curl localhost:8080/reactive/key1
@GetMapping("/{key}")
public Mono<Sample> getByKey(@PathVariable String key) {
return template.selectOneById(key, Sample.class);
}
// curl localhost:8080/reactive/insert -d '{"key":"key1","value":"value1"}' -X PATCH -H 'Content-Type: application/json'
@PatchMapping("/**")
public Mono<Sample> patch(@RequestBody Sample sample) {
return template.insert(sample);
}
// curl localhost:8080/reactive/key1 -X DELETE
@DeleteMapping("/{key}")
public Mono<Boolean> delete(@PathVariable String key) {
return template.deleteById(key, Sample.class);
}
}