1概要

このチュートリアルは、Javaを使ったhttp://cassandra.apache.org/[Apache Cassandra]データベースの入門ガイドです。

JavaからこのNoSQLデータベースに接続して作業を開始するための基本的なステップをカバーする実用的な例と共に、説明された重要な概念を見つけるでしょう。


2カサンドラ

Cassandraは、単一障害点のない継続的な可用性を提供し、並外れたパフォーマンスで大量のデータを処理する機能を提供するスケーラブルなNoSQLデータベースです。

このデータベースはマスタースレーブアーキテクチャを使用する代わりにリングデザインを使用します。リング設計では、マスターノードはありません – 参加しているすべてのノードは同一で、ピアとして互いに通信します。

これにより、Cassandraは、再構成を必要とせずにノードを段階的に追加できるため、水平方向にスケーラブルなシステムになります。


2.1. 主な概念

Cassandraのいくつかの重要な概念についての簡単な調査から始めましょう。


  • クラスタ

    – リング状に配置されたノードまたはデータセンターの集まり

建築。名前はすべてのクラスタに割り当てる必要があります。
その後、参加ノードによって使用される


キースペース** – もしあなたがリレーショナルデータベースから来ているのなら、

schemaはCassandraのそれぞれのキースペースです。キースペースは
Cassandraのデータの最も外側のコンテナ。設定する主な属性
キースペースごとに

Replication Factor



Replica Placementがあります。
戦略と

列の家族__


Column Family ** – CassandraのColumn Familyは、

リレーショナルデータベース各Column Familyは

Map <RowKey、SortedMap <ColumnKey、ColumnValue >>

で表される行のコレクションを含みます。


  • Column

    – Cassandraの列は、を含むデータ構造です。

列名、値、およびタイムスタンプ。各行の列と列数は、データが適切に構造化されているリレーショナルデータベースとは対照的に異なる場合があります。


3 Javaクライアントを使用する


3.1. Mavenの依存関係

次のCassandra依存関係を

pom.xml

に定義する必要があります。その最新バージョンはhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.datastax.cassandraにあります。 %22[ここ]:

<dependency>
    <groupId>com.datastax.cassandra</groupId>
    <artifactId>cassandra-driver-core</artifactId>
    <version>3.1.0</version>
</dependency>

組み込みデータベースサーバーでコードをテストするために、

cassandra-unit

依存関係も追加する必要があります。その最新バージョンはhttps://search.maven.org/classic/#search%7Cga%7C1%7Ccassandra-にあります。単位[ここ]:

<dependency>
    <groupId>org.cassandraunit</groupId>
    <artifactId>cassandra-unit</artifactId>
    <version>3.0.0.1</version>
</dependency>


3.2. Cassandraへの接続

JavaからCassandraに接続するには、

Cluster

オブジェクトを構築する必要があります。

接点としてノードのアドレスを提供する必要があります。ポート番号を入力しないと、デフォルトのポート(9042)が使用されます。

これらの設定により、ドライバはクラスタの現在のトポロジを検出できます。

public class CassandraConnector {

    private Cluster cluster;

    private Session session;

    public void connect(String node, Integer port) {
        Builder b = Cluster.builder().addContactPoint(node);
        if (port != null) {
            b.withPort(port);
        }
        cluster = b.build();

        session = cluster.connect();
    }

    public Session getSession() {
        return this.session;
    }

    public void close() {
        session.close();
        cluster.close();
    }
}


3.3. キースペースの作成



library

」キースペースを作成しましょう。

public void createKeyspace(
  String keyspaceName, String replicationStrategy, int replicationFactor) {
  StringBuilder sb =
    new StringBuilder("CREATE KEYSPACE IF NOT EXISTS ")
      .append(keyspaceName).append(" WITH replication = {")
      .append("'class':'").append(replicationStrategy)
      .append("','replication__factor':").append(replicationFactor)
      .append("};");

    String query = sb.toString();
    session.execute(query);
}


keyspaceName

を除いて、さらに2つのパラメーター、

replicationFactor



replicationStrategy

を定義する必要があります。これらのパラメータは、レプリカの数と、レプリカがリング全体にどのように分配されるかをそれぞれ決定します。

レプリケーションにより、Cassandraはデータのコピーを複数のノードに保存することで信頼性と耐障害性を保証します。

この時点で、キースペースが正しく作成されたことをテストできます。

private KeyspaceRepository schemaRepository;
private Session session;

@Before
public void connect() {
    CassandraConnector client = new CassandraConnector();
    client.connect("127.0.0.1", 9142);
    this.session = client.getSession();
    schemaRepository = new KeyspaceRepository(session);
}

@Test
public void whenCreatingAKeyspace__thenCreated() {
    String keyspaceName = "library";
    schemaRepository.createKeyspace(keyspaceName, "SimpleStrategy", 1);

    ResultSet result =
      session.execute("SELECT **  FROM system__schema.keyspaces;");

    List<String> matchedKeyspaces = result.all()
      .stream()
      .filter(r -> r.getString(0).equals(keyspaceName.toLowerCase()))
      .map(r -> r.getString(0))
      .collect(Collectors.toList());

    assertEquals(matchedKeyspaces.size(), 1);
    assertTrue(matchedKeyspaces.get(0).equals(keyspaceName.toLowerCase()));
}


3.4. 列ファミリを作成する

これで、最初のColumn Family “books”を既存のキースペースに追加できます。

private static final String TABLE__NAME = "books";
private Session session;

public void createTable() {
    StringBuilder sb = new StringBuilder("CREATE TABLE IF NOT EXISTS ")
      .append(TABLE__NAME).append("(")
      .append("id uuid PRIMARY KEY, ")
      .append("title text,")
      .append("subject text);");

    String query = sb.toString();
    session.execute(query);
}

列ファミリが作成されたことをテストするためのコードを以下に示します。

private BookRepository bookRepository;
private Session session;

@Before
public void connect() {
    CassandraConnector client = new CassandraConnector();
    client.connect("127.0.0.1", 9142);
    this.session = client.getSession();
    bookRepository = new BookRepository(session);
}

@Test
public void whenCreatingATable__thenCreatedCorrectly() {
    bookRepository.createTable();

    ResultSet result = session.execute(
      "SELECT **  FROM " + KEYSPACE__NAME + ".books;");

    List<String> columnNames =
      result.getColumnDefinitions().asList().stream()
      .map(cl -> cl.getName())
      .collect(Collectors.toList());

    assertEquals(columnNames.size(), 3);
    assertTrue(columnNames.contains("id"));
    assertTrue(columnNames.contains("title"));
    assertTrue(columnNames.contains("subject"));
}


3.5. コラムファミリを変更する

本には出版社もありますが、作成されたテーブルにそのような列は見つかりません。次のコードを使用してテーブルを変更し、新しい列を追加することができます。

public void alterTablebooks(String columnName, String columnType) {
    StringBuilder sb = new StringBuilder("ALTER TABLE ")
      .append(TABLE__NAME).append(" ADD ")
      .append(columnName).append(" ")
      .append(columnType).append(";");

    String query = sb.toString();
    session.execute(query);
}

新しい列

publisher

が追加されたことを確認しましょう。

@Test
public void whenAlteringTable__thenAddedColumnExists() {
    bookRepository.createTable();

    bookRepository.alterTablebooks("publisher", "text");

    ResultSet result = session.execute(
      "SELECT **  FROM " + KEYSPACE__NAME + "." + "books" + ";");

    boolean columnExists = result.getColumnDefinitions().asList().stream()
      .anyMatch(cl -> cl.getName().equals("publisher"));

    assertTrue(columnExists);
}


3.6. 列ファミリにデータを挿入する


books

テーブルが作成されたので、テーブルにデータを追加する準備が整いました。

public void insertbookByTitle(Book book) {
    StringBuilder sb = new StringBuilder("INSERT INTO ")
      .append(TABLE__NAME__BY__TITLE).append("(id, title) ")
      .append("VALUES (").append(book.getId())
      .append(", '").append(book.getTitle()).append("');");

    String query = sb.toString();
    session.execute(query);
}

「books」テーブルに新しい行が追加されたので、その行が存在するかどうかをテストできます。

@Test
public void whenAddingANewBook__thenBookExists() {
    bookRepository.createTableBooksByTitle();

    String title = "Effective Java";
    Book book = new Book(UUIDs.timeBased(), title, "Programming");
    bookRepository.insertbookByTitle(book);

    Book savedBook = bookRepository.selectByTitle(title);
    assertEquals(book.getTitle(), savedBook.getTitle());
}

上記のテストコードで、__booksByTitleという名前のテーブルを作成するために別の方法を使用しました。

public void createTableBooksByTitle() {
    StringBuilder sb = new StringBuilder("CREATE TABLE IF NOT EXISTS ")
      .append("booksByTitle").append("(")
      .append("id uuid, ")
      .append("title text,")
      .append("PRIMARY KEY (title, id));");

    String query = sb.toString();
    session.execute(query);
}

Cassandraでは、ベストプラクティスの1つは、クエリごとに1つのテーブルを使用することです。つまり、異なるクエリには異なるテーブルが必要です。

この例では、タイトルで本を選択することにしました。

selectByTitle

クエリを満たすために、

title



id

の列を使用して、

PRIMARY KEY

という複合語でテーブルを作成しました。列

title

はパーティション化キーで、

id

列はクラスタ化キーです。

このように、データモデルの多くのテーブルには重複データが含まれています。

これはこのデータベースの欠点ではありません。それどころか、このプラクティスは読み取りのパフォーマンスを最適化します。

現在テーブルに保存されているデータを見てみましょう。

public List<Book> selectAll() {
    StringBuilder sb =
      new StringBuilder("SELECT **  FROM ").append(TABLE__NAME);

    String query = sb.toString();
    ResultSet rs = session.execute(query);

    List<Book> books = new ArrayList<Book>();

    rs.forEach(r -> {
        books.add(new Book(
          r.getUUID("id"),
          r.getString("title"),
          r.getString("subject")));
    });
    return books;
}

期待される結果を返すクエリのテスト:

@Test
public void whenSelectingAll__thenReturnAllRecords() {
    bookRepository.createTable();

    Book book = new Book(
      UUIDs.timeBased(), "Effective Java", "Programming");
    bookRepository.insertbook(book);

    book = new Book(
      UUIDs.timeBased(), "Clean Code", "Programming");
    bookRepository.insertbook(book);

    List<Book> books = bookRepository.selectAll();

    assertEquals(2, books.size());
    assertTrue(books.stream().anyMatch(b -> b.getTitle()
      .equals("Effective Java")));
    assertTrue(books.stream().anyMatch(b -> b.getTitle()
      .equals("Clean Code")));
}

今まではすべて問題ありませんが、1つ認識しておく必要があります。

booksというテーブルで作業を始めましたが、それまでの間、

title

列による

select

クエリを満たすために、

booksByTitle.__という名前の別のテーブルを作成する必要がありました

2つのテーブルは重複した列を含んでいますが、

booksByTitle

テーブルにデータを挿入しただけです。結果として、2つのテーブルのデータは現在矛盾しています。

テーブルごとに1つずつ、2つのinsertステートメントで構成される

batch

クエリを使用してこれを解決できます。

batch

クエリは、単一の操作として複数のDML文を実行します。

そのようなクエリの例を示します。

public void insertBookBatch(Book book) {
    StringBuilder sb = new StringBuilder("BEGIN BATCH ")
      .append("INSERT INTO ").append(TABLE__NAME)
      .append("(id, title, subject) ")
      .append("VALUES (").append(book.getId()).append(", '")
      .append(book.getTitle()).append("', '")
      .append(book.getSubject()).append("');")
      .append("INSERT INTO ")
      .append(TABLE__NAME__BY__TITLE).append("(id, title) ")
      .append("VALUES (").append(book.getId()).append(", '")
      .append(book.getTitle()).append("');")
      .append("APPLY BATCH;");

    String query = sb.toString();
    session.execute(query);
}

ここでも、バッチクエリの結果を次のようにテストします。

@Test
public void whenAddingANewBookBatch__ThenBookAddedInAllTables() {
    bookRepository.createTable();

    bookRepository.createTableBooksByTitle();

    String title = "Effective Java";
    Book book = new Book(UUIDs.timeBased(), title, "Programming");
    bookRepository.insertBookBatch(book);

    List<Book> books = bookRepository.selectAll();

    assertEquals(1, books.size());
    assertTrue(
      books.stream().anyMatch(
        b -> b.getTitle().equals("Effective Java")));

    List<Book> booksByTitle = bookRepository.selectAllBookByTitle();

    assertEquals(1, booksByTitle.size());
    assertTrue(
      booksByTitle.stream().anyMatch(
        b -> b.getTitle().equals("Effective Java")));
}

: As of version 3.0, a new feature called “Materialized Views” is available , which we may use instead of

batch

queries. A well-documented example for “Materialized Views” is available

here

.


3.7. 列ファミリを削除する

以下のコードは、テーブルを削除する方法を示しています。

public void deleteTable() {
    StringBuilder sb =
      new StringBuilder("DROP TABLE IF EXISTS ").append(TABLE__NAME);

    String query = sb.toString();
    session.execute(query);
}

キースペースに存在しないテーブルを選択すると、

InvalidQueryException:unconfigured table books

が発生します。

@Test(expected = InvalidQueryException.class)
public void whenDeletingATable__thenUnconfiguredTable() {
    bookRepository.createTable();
    bookRepository.deleteTable("books");

    session.execute("SELECT **  FROM " + KEYSPACE__NAME + ".books;");
}


3.8. キースペースの削除

最後に、キースペースを削除しましょう。

public void deleteKeyspace(String keyspaceName) {
    StringBuilder sb =
      new StringBuilder("DROP KEYSPACE ").append(keyspaceName);

    String query = sb.toString();
    session.execute(query);
}

そして、キースペースが削除されたことをテストします。

@Test
public void whenDeletingAKeyspace__thenDoesNotExist() {
    String keyspaceName = "library";
    schemaRepository.deleteKeyspace(keyspaceName);

    ResultSet result =
      session.execute("SELECT **  FROM system__schema.keyspaces;");
    boolean isKeyspaceCreated = result.all().stream()
      .anyMatch(r -> r.getString(0).equals(keyspaceName.toLowerCase()));

    assertFalse(isKeyspaceCreated);
}


4結論

このチュートリアルでは、CassandraデータベースにJavaで接続して使用するための基本手順について説明しました。このデータベースの重要な概念のいくつかはまたあなたがキックスタートを助けるために議論されています。

このチュートリアルの完全な実装はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/java-cassandra[Githubプロジェクト]にあります。