Vodič za Cassandru s Javom

1. Pregled

Ovaj je vodič uvodni vodič za bazu podataka Apache Cassandra pomoću Jave.

Objašnjeni su ključni koncepti, zajedno s radnim primjerom koji pokriva osnovne korake za povezivanje i početak rada s ovom NoSQL bazom podataka s Jave.

2. Kasandra

Cassandra je skalabilna baza podataka NoSQL koja pruža kontinuiranu dostupnost bez ijedne točke kvara i daje mogućnost obrade velike količine podataka uz izuzetne performanse.

Ova baza podataka koristi dizajn prstena umjesto da koristi arhitekturu master-slave. U dizajnu prstena ne postoji glavni čvor - svi čvorovi koji sudjeluju identični su i međusobno komuniciraju kao vršnjaci.

To Cassandru čini vodoravno skalabilnim sustavom omogućavajući postupno dodavanje čvorova bez potrebe za rekonfiguracijom.

2.1. Ključni koncepti

Počnimo s kratkim pregledom nekih ključnih pojmova Cassandre:

  • Klastera - zbirka čvorova ili centara podataka raspoređenih u prstenastu arhitekturu. Svakom klasteru mora se dodijeliti ime koje će naknadno koristiti čvorovi koji sudjeluju
  • Tipke - Ako dolazite iz relacijske baze podataka, tada je shema odgovarajući prostor ključeva u Cassandri. Prostor ključeva je najudaljeniji spremnik za podatke u Cassandri. Glavni atributi za postavljanje po prostoru ključeva su Faktor replikacije, Strategija postavljanja replike i Kolumne obitelji
  • Obitelj stupaca - Obitelji stupaca u Cassandri su poput tablica u relacijskim bazama podataka. Svaka obitelj stupaca sadrži zbirku redaka koji su predstavljeni s Karta. Ključ daje mogućnost zajedničkog pristupa povezanim podacima
  • Stupac - Stupac u Cassandri je struktura podataka koja sadrži naziv stupca, vrijednost i vremensku oznaku. Stupci i broj stupaca u svakom retku mogu se razlikovati za razliku od relacijske baze podataka u kojoj su podaci dobro strukturirani

3. Korištenje Java klijenta

3.1. Ovisnost Mavena

Moramo definirati sljedeću ovisnost Cassandre u pom.xml, čiju najnoviju verziju možete pronaći ovdje:

 com.datastax.cassandra cassandra-driver-core 3.1.0 

Da bismo testirali kod s ugrađenim poslužiteljem baze podataka, trebali bismo dodati i kasandra-jedinica ovisnost, čiju najnoviju verziju možete pronaći ovdje:

 org.cassandraunit kasandra-jedinica 3.0.0.1 

3.2. Povezivanje s Cassandrom

Da bismo se povezali s Cassandrom s Jave, moramo izgraditi Klastera objekt.

Adresa čvora mora biti navedena kao kontaktna točka. Ako ne navedemo broj porta, koristit će se zadani port (9042).

Te postavke omogućuju vozaču da otkrije trenutnu topologiju klastera.

javna klasa CassandraConnector {klaster privatnog klastera; privatna sjednica; javna void veza (čvor niza, cjeloviti port) {Builder b = Cluster.builder (). addContactPoint (čvor); if (port! = null) {b.withPort (port); } klaster = b.build (); session = cluster.connect (); } javna sesija getSession () {return this.session; } javna void close () {session.close (); cluster.close (); }}

3.3. Stvaranje prostora tipki

Stvorimo svoj “knjižnica”Prostor tipki:

javna praznina createKeyspace (String keyspaceName, String replicationStrategy, int replicationFactor) {StringBuilder sb = new StringBuilder ("CREATE KEYSPACE IF NOT EXISTS") .append (keyspaceName) .append ("WITH replikacija = {") .append (" : '"). append (replicationStrategy) .append ("', 'replication_factor': "). append (replicationFactor) .append ("}; "); String upit = sb.toString (); session.execute (upit); }

Osim iz keyspaceName moramo definirati još dva parametra, faktor replikacije i replikacijaStrategija. Ovi parametri određuju broj replika i način na koji će se replike rasporediti po prstenu.

Replikacijom Cassandra osigurava pouzdanost i otpornost na greške spremanjem kopija podataka u više čvorova.

U ovom trenutku možemo testirati je li naš prostor ključeva uspješno stvoren:

privatni KeyspaceRepository schemaRepository; privatna sjednica; @Prije javne void connect () {CassandraConnector client = novi CassandraConnector (); client.connect ("127.0.0.1", 9142); this.session = client.getSession (); schemaRepository = novi KeyspaceRepository (sesija); }
@Test public void whenCreatingAKeyspace_thenCreated () {String keyspaceName = "library"; schemaRepository.createKeyspace (keyspaceName, "SimpleStrategy", 1); RezultatSet rezultat = session.execute ("SELECT * FROM system_schema.keyspaces;"); Popis poklapanihKeyspaces = 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. Stvaranje obitelji stupaca

Sada možemo dodati prve "knjige" iz Obitelji stupaca u postojeći prostor ključeva:

privatni statički završni niz TABLE_NAME = "knjige"; privatna sjednica; javna praznina createTable () {StringBuilder sb = new StringBuilder ("STVORI TABLICU AKO NE POSTOJI") .append (TABLE_NAME) .append ("(") .append ("id uuid PRIMARNI KLJUČ,") .append ("tekst naslova, ") .append (" tekst predmeta); "); String upit = sb.toString (); session.execute (upit); }

Kôd za testiranje stvorene obitelji stupaca nalazi se u nastavku:

privatni BookRepository bookRepository; privatna sjednica; @ Prije javne void connect () {CassandraConnector client = novi CassandraConnector (); client.connect ("127.0.0.1", 9142); this.session = client.getSession (); bookRepository = novo BookRepository (sesija); }
@Test public void whenCreatingATable_thenCreatedCorrect () {bookRepository.createTable (); RezultatSet rezultat = session.execute ("SELECT * FROM" + KEYSPACE_NAME + ".books;"); Lista stupacaName = 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. Promjena obitelji stupaca

Knjiga ima i izdavača, ali u stvorenoj tablici ne može se naći takav stupac. Sljedeći kod možemo koristiti za izmjenu tablice i dodavanje novog stupca:

javna void alterTablebooks (StringName stupca, Niz stupcaTip) {StringBuilder sb = novi StringBuilder ("ALTER TABLE") .append (TABLE_NAME) .append ("ADD") .append (columnName) .append ("") .append (columnType) .dodati(";"); String upit = sb.toString (); session.execute (upit); }

Pobrinimo se da novi stupac izdavač dodano je:

@Test public void whenAlteringTable_thenAddedColumnExists () {bookRepository.createTable (); bookRepository.alterTablebooks ("izdavač", "tekst"); RezultatSet rezultat = session.execute ("SELECT * FROM" + KEYSPACE_NAME + "." + "Books" + ";"); boolean columnExists = result.getColumnDefinitions (). asList (). stream () .anyMatch (cl -> cl.getName (). jednako ("izdavač")); assertTrue (columnExists); }

3.6. Umetanje podataka u obitelj stupaca

Sad kad je knjige stvorena je tablica, spremni smo za dodavanje podataka u tablicu:

public void insertbookByTitle (Knjiga knjiga) {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 upit = sb.toString (); session.execute (upit); }

U tablicu 'knjige' dodan je novi redak, pa možemo testirati postoji li taj redak:

@Test public void whenAddingANewBook_thenBookExists () {bookRepository.createTableBooksByTitle (); Naslov niza = "Učinkovita Java"; Knjiga knjiga = nova knjiga (UUIDs.timeBased (), naslov, "Programiranje"); bookRepository.insertbookByTitle (knjiga); Knjiga je spremljenaBook = bookRepository.selectByTitle (naslov); assertEquals (book.getTitle (), savedBook.getTitle ()); }

U gore navedenom testnom kodu koristili smo drugačiju metodu za stvaranje tablice s imenom booksByTitle:

public void createTableBooksByTitle () {StringBuilder sb = new StringBuilder ("STVORI TABLICU AKO NE POSTOJI") .append ("booksByTitle"). append ("(") .append ("id uuid,") .append ("tekst u naslovu, ") .append (" PRIMARNI KLJUČ (naslov, id)); "); String upit = sb.toString (); session.execute (upit); }

U Cassandri je jedan od najboljih postupaka korištenje uzorka jedne tablice po upitu. To znači da je za drugačiji upit potrebna druga tablica.

U našem smo primjeru knjigu odabrali prema naslovu. Kako bi se zadovoljila selectByTitle upit, stvorili smo tablicu sa spojem OSNOVNI KLJUČ koristeći stupce, titula i iskaznica. Kolona titula je particijski ključ dok je iskaznica stupac je ključ klasteriranja.

Na taj način mnoge tablice u vašem podatkovnom modelu sadrže duplicirane podatke. To nije loša strana ove baze podataka. Naprotiv, ova praksa optimizira izvedbu čitanja.

Pogledajmo podatke koji su trenutno spremljeni u našoj tablici:

javni popis selectAll () {StringBuilder sb = new StringBuilder ("SELECT * FROM") .append (TABLE_NAME); String upit = sb.toString (); ResultSet rs = session.execute (upit); Popis knjiga = novi ArrayList (); rs.forEach (r -> {books.add (nova Knjiga (r.getUUID ("id"), r.getString ("naslov"), r.getString ("predmet")));}); vratiti knjige; }

Test za upit koji vraća očekivane rezultate:

@Test public void whenSelectingAll_thenReturnAllRecords () {bookRepository.createTable (); Knjiga knjiga = nova knjiga (UUIDs.timeBased (), "Učinkovita Java", "Programiranje"); bookRepository.insertbook (knjiga); book = nova knjiga (UUIDs.timeBased (), "Clean Code", "Programming"); bookRepository.insertbook (knjiga); Popis knjiga = bookRepository.selectAll (); assertEquals (2, books.size ()); assertTrue (books.stream (). anyMatch (b -> b.getTitle () .equals ("Učinkovita Java"))); assertTrue (books.stream (). anyMatch (b -> b.getTitle () .equals ("Clean Code"))); }

Do sada je sve u redu, ali jedno se mora shvatiti. Počeli smo raditi sa stolom knjige, ali u međuvremenu, kako bi se zadovoljila Odaberi upit putem titula stupac, morali smo stvoriti drugu tablicu s imenom booksByTitle.

Dvije su tablice identične i sadrže duplicirane stupce, ali podatke smo umetnuli samo u booksByTitle stol. Kao posljedica toga, podaci u dvije tablice trenutno su nedosljedni.

To možemo riješiti pomoću a šarža upit koji sadrži dvije izjave za umetanje, po jednu za svaku tablicu. A šarža upit izvršava više DML izraza kao jednu operaciju.

Navodi se primjer takvog upita:

javna void insertBookBatch (knjiga knjiga) {StringBuilder sb = new StringBuilder ("BEGIN BATCH") .append ("INSERT INTO") .append ("TABLE_NAME") .append ("(id, title, subject)") .append ("VRIJEDNOSTI (") .append (book.getId ()). append (", '") .append (book.getTitle ()). append ("', '") .append (book.getSubject ()). append ( "');") .append ("INSERT INTO") .append (TABLE_NAME_BY_TITLE) .append ("(id, title)") .append ("VRIJEDNOSTI (") .append (book.getId ()). append ( ", '") .append (book.getTitle ()). append ("');") .append ("APPLY BATCH;"); String upit = sb.toString (); session.execute (upit); }

Ponovno testiramo rezultate skupnog upita na sljedeći način:

@Test public void whenAddingANewBookBatch_ThenBookAddedInAllTables () {bookRepository.createTable (); bookRepository.createTableBooksByTitle (); Naslov niza = "Učinkovita Java"; Knjiga knjiga = nova knjiga (UUIDs.timeBased (), naslov, "Programiranje"); bookRepository.insertBookBatch (knjiga); Popis knjiga = bookRepository.selectAll (); assertEquals (1, books.size ()); assertTrue (books.stream (). anyMatch (b -> b.getTitle (). jednako ("Učinkovita Java"))); Popis booksByTitle = bookRepository.selectAllBookByTitle (); assertEquals (1, booksByTitle.size ()); assertTrue (booksByTitle.stream (). anyMatch (b -> b.getTitle (). jednako ("Učinkovita Java"))); }

Napomena: Od verzije 3.0 dostupna je nova značajka nazvana "Materijalizirani pogledi" koju bismo mogli koristiti umjesto nje šarža upiti. Dobro dokumentirani primjer za "Materijalizirane poglede" dostupan je ovdje.

3.7. Brisanje obitelji stupaca

Kôd u nastavku prikazuje kako izbrisati tablicu:

public void deleteTable () {StringBuilder sb = new StringBuilder ("DROP TABLE IF EXISTS") .append (TABLE_NAME); String upit = sb.toString (); session.execute (upit); }

Odabir tablice koja ne postoji u prostoru ključeva rezultira znakom InvalidQueryException: nekonfigurirane knjige tablica:

@Test (očekuje se = InvalidQueryException.class) javna praznina kadaDeletingATable_thenUnconfiguredTable () {bookRepository.createTable (); bookRepository.deleteTable ("knjige"); session.execute ("SELECT * FROM" + KEYSPACE_NAME + ".books;"); }

3.8. Brisanje prostora tipki

Na kraju, izbrišimo prostor tipki:

javna praznina deleteKeyspace (StringSpaceName) {StringBuilder sb = new StringBuilder ("DROP KEYSPACE") .append (keyspaceName); String upit = sb.toString (); session.execute (upit); }

I provjerite je li prostor tipki izbrisan:

@Test public void whenDeletingAKeyspace_thenDoesNotExist () {String keyspaceName = "library"; schemaRepository.deleteKeyspace (keyspaceName); RezultatSet rezultat = session.execute ("SELECT * FROM system_schema.keyspaces;"); boolean isKeyspaceCreated = result.all (). stream () .anyMatch (r -> r.getString (0) .equals (keyspaceName.toLowerCase ())); assertFalse (isKeyspaceCreated); }

4. Zaključak

Ovaj je vodič obuhvatio osnovne korake povezivanja i korištenja baze podataka Cassandra s Javom. O nekim ključnim konceptima ove baze podataka također je bilo riječi kako bi vam pomogli da započnete.

Potpuna provedba ovog vodiča može se naći u projektu Github.