En guide til Cassandra med Java

1. Oversikt

Denne opplæringen er en innledende guide til Apache Cassandra-databasen ved hjelp av Java.

Du vil finne viktige begreper forklart, sammen med et arbeidseksempel som dekker de grunnleggende trinnene for å koble til og begynne å jobbe med denne NoSQL-databasen fra Java.

2. Cassandra

Cassandra er en skalerbar NoSQL-database som gir kontinuerlig tilgjengelighet uten et eneste feilpunkt og gir muligheten til å håndtere store datamengder med eksepsjonell ytelse.

Denne databasen bruker en ringdesign i stedet for å bruke en master-slave-arkitektur. I ringdesignet er det ingen masternode - alle deltakende noder er identiske og kommuniserer med hverandre som jevnaldrende.

Dette gjør Cassandra til et horisontalt skalerbart system ved å tillate inkrementell tillegg av noder uten å måtte omkonfigureres.

2.1. Nøkkelkonsepter

La oss starte med en kort undersøkelse av noen av nøkkelbegrepene i Cassandra:

  • Klynge - en samling noder eller datasentre arrangert i en ringarkitektur. Et navn må tildeles hver klynge, som deretter vil bli brukt av de deltakende nodene
  • Keyspace - Hvis du kommer fra en relasjonsdatabase, er skjemaet det respektive nøkkelområdet i Cassandra. Nøkkelområdet er den ytterste beholderen for data i Cassandra. Hovedattributtene som skal angis per nøkkelområde er Replikasjonsfaktor, den Replikaplasseringsstrategi og Kolonnefamilier
  • Kolonnefamilie - Kolonnefamilier i Cassandra er som tabeller i Relasjonsdatabaser. Hver kolonnefamilie inneholder en samling rader som er representert med en Kart. Nøkkelen gir muligheten til å få tilgang til relaterte data sammen
  • Kolonne - En kolonne i Cassandra er en datastruktur som inneholder et kolonnenavn, en verdi og et tidsstempel. Kolonnene og antall kolonner i hver rad kan variere i kontrast til en relasjonsdatabase der data er godt strukturert

3. Bruke Java Client

3.1. Maven avhengighet

Vi må definere følgende Cassandra-avhengighet i pom.xml, den siste versjonen av den finner du her:

 com.datastax.cassandra cassandra-driver-core 3.1.0 

For å teste koden med en innebygd databaseserver, bør vi også legge til kassandra-enhet avhengighet, den siste versjonen finner du her:

 org.cassandraunit cassandra-unit 3.0.0.1 

3.2. Kobler til Cassandra

For å koble til Cassandra fra Java, må vi bygge en Klynge gjenstand.

En adresse til en node må oppgis som et kontaktpunkt. Hvis vi ikke oppgir et portnummer, vil standardporten (9042) brukes.

Disse innstillingene lar sjåføren oppdage den nåværende topologien til en klynge.

offentlig klasse CassandraConnector {private klyngeklynger; privat øktøkt; public void connect (String node, Integer port) {Builder b = Cluster.builder (). addContactPoint (node); hvis (port! = null) {b.withPort (port); } klynge = b.build (); økt = cluster.connect (); } public Session getSession () {return this.session; } offentlig ugyldig lukk () {session.close (); cluster.close (); }}

3.3. Opprette Keyspace

La oss lage vår “bibliotek”Nøkkelplass:

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 ("}; "); Strengspørsmål = sb.toString (); session.execute (spørring); }

Bortsett fra keypaceName vi må definere to parametere til, replicationFactor og replikeringStrategi. Disse parametrene bestemmer antall replikaer og hvordan replikaene skal fordeles over henholdsvis ringen.

Med replikasjon sikrer Cassandra pålitelighet og feiltoleranse ved å lagre kopier av data i flere noder.

På dette tidspunktet kan vi teste at nøkkelområdet vårt er opprettet:

private KeyspaceRepository schemaRepository; privat øktøkt; @ Før offentlig ugyldig tilkobling () {CassandraConnector-klient = ny CassandraConnector (); client.connect ("127.0.0.1", 9142); this.session = client.getSession (); schemaRepository = nytt KeyspaceRepository (økt); }
@Test offentlig ugyldig nårCreatingAKeyspace_thenCreated () {String keyspaceName = "library"; schemaRepository.createKeyspace (keyspaceName, "SimpleStrategy", 1); ResultSet resultat = session.execute ("SELECT * FROM system_schema.keyspaces;"); Liste 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. Opprette en kolonnefamilie

Nå kan vi legge til de første kolonnefamiliebøkene i det eksisterende nøkkelområdet:

privat statisk slutt String TABLE_NAME = "bøker"; privat øktøkt; 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 (" emnetekst); "); Strengspørsmål = sb.toString (); session.execute (spørring); }

Koden for å teste at kolonnefamilien er opprettet, er gitt nedenfor:

private BookRepository bookRepository; privat øktøkt; @ Før offentlig ugyldig tilkobling () {CassandraConnector-klient = ny CassandraConnector (); client.connect ("127.0.0.1", 9142); this.session = client.getSession (); bookRepository = nytt BookRepository (økt); }
@Test offentlig ugyldig nårCreatingATable_thenCreatedCorrectly () {bookRepository.createTable (); ResultSet resultat = session.execute ("SELECT * FROM" + KEYSPACE_NAME + ".books;"); Vis kolonnenavn = resultat.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. Endre kolonnefamilien

En bok har også en forlegger, men ingen slike kolonner finnes i den opprettede tabellen. Vi kan bruke følgende kode for å endre tabellen og legge til en ny kolonne:

public void alterTablebooks (String columnName, String columnType) {StringBuilder sb = new StringBuilder ("ALTER TABLE") .append (TABLE_NAME) .append ("ADD") .append (columnName) .append ("") .append (columnType) .append (";"); Strengspørsmål = sb.toString (); session.execute (spørring); }

La oss sørge for at den nye kolonnen forlegger har blitt lagt til:

@Test offentlig ugyldig nårAlteringTable_thenAddedColumnExists () {bookRepository.createTable (); bookRepository.alterTablebooks ("utgiver", "tekst"); ResultSet result = session.execute ("SELECT * FROM" + KEYSPACE_NAME + "." + "Books" + ";"); boolske columnExists = result.getColumnDefinitions (). asList (). stream () .anyMatch (cl -> cl.getName (). equals ("publisher")); assertTrue (columnExists); }

3.6. Sette inn data i kolonnefamilien

Nå som bøker tabellen er opprettet, er vi klare til å begynne å legge til data i tabellen:

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 ("');"); Strengspørsmål = sb.toString (); session.execute (spørring); }

En ny rad er lagt til i «bøker» -tabellen, slik at vi kan teste om raden eksisterer:

@Test offentlig ugyldig nårAddingANewBook_thenBookExists () {bookRepository.createTableBooksByTitle (); Streng tittel = "Effektiv Java"; Bokbok = ny bok (UUIDs.timeBased (), tittel, "Programmering"); bookRepository.insertbookByTitle (bok); Bok lagretBok = bookRepository.selectByTitle (tittel); assertEquals (book.getTitle (), lagretBook.getTitle ()); }

I testkoden ovenfor har vi brukt en annen metode for å lage en tabell med navnet booksByTitle:

public void createTableBooksByTitle () {StringBuilder sb = new StringBuilder ("CREATE TABLE IF NOT EXISTS") .append ("booksByTitle"). append ("(") .append ("id uuid,") .append ("title text, ") .append (" PRIMÆR NØKKEL (tittel, id)); "); Strengspørsmål = sb.toString (); session.execute (spørring); }

I Cassandra er en av de beste metodene å bruke mønster med én tabell per spørring. Dette betyr at for en annen spørring er det behov for en annen tabell.

I vårt eksempel har vi valgt å velge en bok etter tittelen. For å tilfredsstille velgByTitle spørring, har vi laget en tabell med en sammensatt PRIMÆRNØKKEL ved hjelp av kolonnene, tittel og id. Kolonnen tittel er partisjoneringsnøkkelen mens id kolonne er klyngenøkkelen.

På denne måten inneholder mange av tabellene i datamodellen duplikatdata. Dette er ikke en ulempe med denne databasen. Tvert imot optimaliserer denne praksisen ytelsen til lesningene.

La oss se dataene som for øyeblikket er lagret i tabellen vår:

public List selectAll () {StringBuilder sb = new StringBuilder ("SELECT * FROM") .append (TABLE_NAME); Strengspørsmål = sb.toString (); ResultSet rs = session.execute (spørring); Listebøker = ny ArrayList (); rs.forEach (r -> {books.add (ny bok (r.getUUID ("id"), r.getString ("tittel"), r.getString ("emne"));}); returnere bøker; }

En test for spørring som returnerer forventede resultater:

@Test offentlig ugyldig nårSelectingAll_thenReturnAllRecords () {bookRepository.createTable (); Bokbok = ny bok (UUIDs.timeBased (), "Effektiv Java", "Programmering"); bookRepository.insertbook (bok); bok = ny bok (UUIDs.timeBased (), "Clean Code", "Programming"); bookRepository.insertbook (bok); Listebøker = bookRepository.selectAll (); assertEquals (2, books.size ()); assertTrue (books.stream (). anyMatch (b -> b.getTitle () .equals ("Effektiv Java"))); assertTrue (books.stream (). anyMatch (b -> b.getTitle () .equals ("Clean Code"))); }

Alt er bra til nå, men en ting må realiseres. Vi begynte å jobbe med bord bøker, men i mellomtiden, for å tilfredsstille å velge spørring av tittel kolonne, måtte vi lage en annen tabell som heter booksByTitle.

De to tabellene er identiske som inneholder dupliserte kolonner, men vi har bare satt inn data i booksByTitle bord. Som en konsekvens er data i to tabeller for tiden inkonsekvente.

Vi kan løse dette ved hjelp av en parti spørring, som består av to innleggssetninger, en for hver tabell. EN parti spørring utfører flere DML-setninger som en enkelt operasjon.

Et eksempel på slike spørsmål er gitt:

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;"); Strengspørsmål = sb.toString (); session.execute (spørring); }

Igjen tester vi batchresultatene slik:

@Test offentlig ugyldig nårAddingANewBookBatch_ThenBookAddedInAllTables () {bookRepository.createTable (); bookRepository.createTableBooksByTitle (); Streng tittel = "Effektiv Java"; Bokbok = ny bok (UUIDs.timeBased (), tittel, "Programmering"); bookRepository.insertBookBatch (bok); Listebøker = bookRepository.selectAll (); assertEquals (1, books.size ()); assertTrue (books.stream (). anyMatch (b -> b.getTitle (). tilsvarer ("Effektiv Java"))); Liste booksByTitle = bookRepository.selectAllBookByTitle (); assertEquals (1, booksByTitle.size ()); assertTrue (booksByTitle.stream (). anyMatch (b -> b.getTitle (). tilsvarer ("Effektiv Java"))); }

Merk: Fra og med versjon 3.0 er en ny funksjon kalt “Materialized Views” tilgjengelig, som vi kan bruke i stedet for parti spørsmål. Et godt dokumentert eksempel for “Materialiserte visninger” er tilgjengelig her.

3.7. Slette kolonnefamilien

Koden nedenfor viser hvordan du sletter en tabell:

public void deleteTable () {StringBuilder sb = new StringBuilder ("DROP TABLE IF EXISTS") .append (TABLE_NAME); Strengspørsmål = sb.toString (); session.execute (spørring); }

Å velge en tabell som ikke finnes i nøkkelområdet, resulterer i en InvalidQueryException: ukonfigurerte tabellbøker:

@Test (forventet = InvalidQueryException.class) offentlig ugyldig nårDeletingATable_thenUnconfiguredTable () {bookRepository.createTable (); bookRepository.deleteTable ("bøker"); session.execute ("SELECT * FROM" + KEYSPACE_NAME + ".books;"); }

3.8. Slette Keyspace

Til slutt, la oss slette nøkkelområdet:

public void deleteKeyspace (String keyspaceName) {StringBuilder sb = new StringBuilder ("DROP KEYSPACE") .append (keyspaceName); Strengspørsmål = sb.toString (); session.execute (spørring); }

Og test at nøkkelområdet er slettet:

@Test offentlig ugyldig nårDeletingAKeyspace_thenDoesNotExist () {String keyspaceName = "library"; schemaRepository.deleteKeyspace (keypaceName); ResultSet resultat = session.execute ("SELECT * FROM system_schema.keyspaces;"); boolsk isKeyspaceCreated = result.all (). stream () .anyMatch (r -> r.getString (0) .equals (keyspaceName.toLowerCase ())); assertFalse (isKeyspaceCreated); }

4. Konklusjon

Denne opplæringen dekket de grunnleggende trinnene for å koble til og bruke Cassandra-databasen med Java. Noen av hovedbegrepene i denne databasen har også blitt diskutert for å hjelpe deg i gang.

Den fulle implementeringen av denne veiledningen finner du i Github-prosjektet.