מדריך לקאסנדרה עם ג'אווה

1. סקירה כללית

מדריך זה מהווה מדריך היכרות למסד הנתונים של Apache Cassandra באמצעות Java.

תוכלו למצוא מושגי מפתח מוסברים, יחד עם דוגמה לעבודה המכסה את השלבים הבסיסיים להתחברות ולהתחלה לעבוד עם מסד נתונים זה NoSQL מ- Java.

2. קסנדרה

קסנדרה הינה מסד נתונים מתרחש של NoSQL המספק זמינות רציפה ללא נקודת כשל אחת ומספק את היכולת להתמודד עם כמויות גדולות של נתונים עם ביצועים יוצאי דופן.

מסד נתונים זה משתמש בעיצוב טבעת במקום להשתמש בארכיטקטורה של עבד-אב. בתכנון הטבעת אין צומת ראשי - כל הצמתים המשתתפים זהים ומתקשרים ביניהם כעמיתים.

זה הופך את קסנדרה למערכת מדרגתית אופקית בכך שהיא מאפשרת תוספת מצטברת של צמתים מבלי להזדקק לתצורה מחדש.

2.1. מושגי מפתח

נתחיל בסקר קצר של כמה ממושגי המפתח של קסנדרה:

  • אֶשׁכּוֹל - אוסף של צמתים או מרכזי נתונים המסודרים בארכיטקטורת טבעות. יש להקצות שם לכל אשכול, אשר לאחר מכן ישמש את הצמתים המשתתפים
  • מרחב מפתחות - אם אתה בא ממסד נתונים יחסי, הסכימה היא מרחב המפתחות בהתאמה בקסנדרה. מרחב המפתחות הוא המכולה החיצונית ביותר עבור נתונים בקסנדרה. התכונות העיקריות להגדרה לכל מרחב מפתחות הן גורם שכפול, ה אסטרטגיית מיקום להעתקים וה משפחות טור
  • משפחת טור - משפחות עמודים בקסנדרה הן כמו טבלאות במאגרי מידע יחסיים. כל משפחת עמודים מכילה אוסף של שורות המיוצגות על ידי מַפָּה. המפתח נותן את היכולת לגשת יחד לנתונים קשורים
  • טור - עמוד בקסנדרה הוא מבנה נתונים המכיל שם עמודה, ערך וחותמת זמן. העמודות ומספר העמודות בכל שורה עשויות להשתנות בניגוד למסד נתונים יחסי שבו הנתונים מובנים היטב

3. שימוש בלקוח Java

3.1. תלות של Maven

עלינו להגדיר את התלות הבאה בקסנדרה ב pom.xml, הגרסה האחרונה שלה ניתן למצוא כאן:

 com.datastax.cassandra קסנדרה-ליבת דרייבר 3.1.0 

על מנת לבדוק את הקוד עם שרת מסד נתונים מוטבע עלינו להוסיף את ה- יחידת קסנדרה תלות, שאת הגרסה האחרונה אפשר למצוא כאן:

 org.cassandraunit cassandra-unit 3.0.0.1 

3.2. מתחבר לקאסנדרה

על מנת להתחבר לקאסנדרה מג'אווה, עלינו לבנות a אֶשׁכּוֹל לְהִתְנַגֵד.

יש לספק כתובת של צומת כנקודת קשר. אם אנו לא מספקים מספר יציאה, ישמש את יציאת ברירת המחדל (9042).

הגדרות אלה מאפשרות לנהג לגלות את הטופולוגיה הנוכחית של אשכול.

מחלקה ציבורית CassandraConnector {אשכול אשכול פרטי; מושב מושב פרטי; חיבור חלל ציבורי (צומת מחרוזת, יציאת מספר שלם) {Builder b = Cluster.builder (). addContactPoint (node); אם (port! = null) {b.withPort (port); } אשכול = b.build (); session = cluster.connect (); } מושב ציבורי getSession () {להחזיר this.session; } חלל ציבורי סגור () {session.close (); cluster.close (); }}

3.3. יצירת מרחב המפתחות

בואו ניצור את “סִפְרִיָהמרחב המפתחות:

חלל ציבורי createKeyspace (String keypaceName, String replicationStrategy, int replicationFactor) {StringBuilder sb = StringBuilder new ("CREATE KEYSPACE IF NOT EXISTS") .append (keypaceName) .append ("WITH replication = {") .append ("'class') : '"). append (replicationStrategy) .append ("', 'replication_factor': "). append (replicationFactor) .append ("}; "); שאילתת מחרוזת = sb.toString (); session.execute (שאילתה); }

פרט ל keypaceName עלינו להגדיר שני פרמטרים נוספים, ה- replicationFactor וה replicationStrategy. פרמטרים אלה קובעים את מספר ההעתקים ואופן חלוקת העתקים על פני הטבעת, בהתאמה.

עם שכפול קסנדרה מבטיחה אמינות וסובלנות תקלות על ידי אחסון עותקי נתונים במספר צמתים.

בשלב זה אנו עשויים לבדוק שמרחב המפתחות שלנו נוצר בהצלחה:

סכימת KeyspaceRepository פרטיתמאגר; מושב מושב פרטי; @ לפני התחברות הריק הציבורי () {לקוח CassandraConnector = CassandraConnector חדש (); client.connect ("127.0.0.1", 9142); this.session = client.getSession (); schemaRepository = KeyspaceRepository חדש (הפעלה); }
@ מבחן ציבורי בטל כאשרCreatingAKeyspace_thenCreated () {String keyspaceName = "ספרייה"; schemaRepository.createKeyspace (keypaceName, "SimpleStrategy", 1); ResultSet result = session.execute ("SELECT * FROM system_schema.keyspaces;"); רשימה matchedKeyspaces = result.all () .stream () .filter (r -> r.getString (0) .equals (keyspaceName.toLowerCase ())) .מפה (r -> r.getString (0)) .collect ( Collectors.toList ()); assertEquals (matchedKeyspaces.size (), 1); assertTrue (matchedKeyspaces.get (0) .equals (keyspaceName.toLowerCase ())); }

3.4. יצירת משפחת טור

כעת נוכל להוסיף את "הספרים" הראשונים של משפחת העמודות למרחב המפתחות הקיים:

סופי סטטי פרטי מחרוזת TABLE_NAME = "ספרים"; מושב מושב פרטי; בטל ציבורי createTable () {StringBuilder sb = StringBuilder חדש ("צור טבלה אם לא קיים") .append (TABLE_NAME) .append ("(") .append ("id uuid PRIMARY KEY,"). ") .append (" טקסט הנושא); "); שאילתת מחרוזת = sb.toString (); session.execute (שאילתה); }

הקוד לבדיקה שנוצרה משפחת העמודות מופיע להלן:

פרטית BookRepository bookRepository; מושב מושב פרטי; @ לפני התחברות הריק הציבורי () {לקוח CassandraConnector = CassandraConnector חדש (); client.connect ("127.0.0.1", 9142); this.session = client.getSession (); bookRepository = BookRepository חדש (הפעלה); }
@ מבחן ציבורי בטל כאשרCreatingATable_thenCreatedCorrectly () {bookRepository.createTable (); ResultSet result = session.execute ("SELECT * FROM" + KEYSPACE_NAME + ".books;"); רשימת columnNames = result.getColumnDefinitions (). AsList (). Stream () .map (cl -> cl.getName ()) .collect (Collectors.toList ()); assertEquals (columnNames.size (), 3); assertTrue (columnNames.contains ("id")); assertTrue (columnNames.contains ("כותרת")); assertTrue (columnNames.contains ("נושא")); }

3.5. שינוי משפחת הטורים

בספר יש גם הוצאה לאור, אך לא ניתן למצוא עמודה כזו בטבלה שנוצרה. אנו יכולים להשתמש בקוד הבא כדי לשנות את הטבלה ולהוסיף עמודה חדשה:

public void alterTablebooks (StringBuilderName, String columnType) {StringBuilder sb = new StringBuilder ("ALTER TABLE") .append (TABLE_NAME) .append ("ADD") .append (columnName) .append ("") .append (columnType) .לְצַרֵף(";"); שאילתת מחרוזת = sb.toString (); session.execute (שאילתה); }

בואו נוודא שהעמודה החדשה מוֹצִיא לָאוֹר התווסף:

@Test ציבורי בטל כאשרAlteringTable_thenAddedColumnExists () {bookRepository.createTable (); bookRepository.alterTablebooks ("מו"ל", "טקסט"); ResultSet result = session.execute ("SELECT * FROM" + KEYSPACE_NAME + "." + "Books" + ";"); column booleanExists = result.getColumnDefinitions (). asList (). stream () .anyMatch (cl -> cl.getName (). שווה ("publisher")); assertTrue (columnExists); }

3.6. הכנסת נתונים למשפחת העמודות

עכשיו כשה- ספרים הטבלה נוצרה, אנו מוכנים להתחיל להוסיף נתונים לטבלה:

insertbook ריק בטל ציבורי (ספר ספרים) {StringBuilder sb = StringBuilder חדש ("INSERT INTO"). הוסף (TABLE_NAME_BY_TITLE). append ("(id, title)"). append ("VALUES ("). append (book.getId ( )) .append (", '") .append (book.getTitle ()). append ("');"); שאילתת מחרוזת = sb.toString (); session.execute (שאילתה); }

בשורה 'ספרים' נוספה שורה חדשה, כדי שנוכל לבדוק אם השורה קיימת:

@ מבחן ציבורי בטל כאשר AddINGANewBook_thenBookExists () {bookRepository.createTableBooksByTitle (); כותרת מחרוזת = "Java יעילה"; ספר ספרים = ספר חדש (UUIDs.timeBased (), כותרת, "תכנות"); bookRepository.insertbookByTitle (ספר); ספר שמור ספר = bookRepository.selectByTitle (כותרת); assertEquals (book.getTitle (), saveBook.getTitle ()); }

בקוד הבדיקה שלמעלה השתמשנו בשיטה אחרת כדי ליצור טבלה בשם booksByTitle:

בטל ציבורי createTableBooksByTitle () {StringBuilder sb = StringBuilder חדש ("צור טבלה אם לא קיים") .append ("booksByTitle"). הוסף ("(") .append ("id uuid,"). הוסף ("טקסט כותרת, ") .append (" מפתח ראשוני (כותרת, מזהה)); "); שאילתת מחרוזת = sb.toString (); session.execute (שאילתה); }

בקסנדרה אחת השיטות המומלצות היא להשתמש בתבנית טבלה אחת לשאילתה. פירוש הדבר, עבור שאילתה אחרת יש צורך בטבלה אחרת.

בדוגמה שלנו, בחרנו לבחור ספר לפי שמו. על מנת לספק את בחר ByTitle שאילתה, יצרנו טבלה עם תרכובת מפתח ראשי באמצעות העמודות, כותרת ו תְעוּדַת זֶהוּת. הטור כותרת הוא מפתח החלוקה בעוד ש- תְעוּדַת זֶהוּת העמודה היא מפתח האשכולות.

בדרך זו, רבים מהטבלאות במודל הנתונים שלך מכילות נתונים כפולים. זה לא חסרון של מסד נתונים זה. להפך, פרקטיקה זו מייעלת את ביצועי הקריאות.

בואו נראה את הנתונים שנשמרים כרגע בטבלה שלנו:

רשימה ציבורית selectAll () {StringBuilder sb = StringBuilder חדש ("SELECT * FROM") .append (TABLE_NAME); שאילתת מחרוזת = sb.toString (); ResultSet rs = session.execute (שאילתה); רשימת ספרים = ArrayList חדש (); rs.forEach (r -> {books.add (ספר חדש (r.getUUID ("id"), r.getString ("כותרת"), r.getString ("נושא")))}}; להחזיר ספרים; }

מבחן לשאילתת החזרת תוצאות צפויות:

@Test ציבורי בטל כאשר בחר All_thenReturnAllRecords () {bookRepository.createTable (); ספר ספרים = ספר חדש (UUIDs.timeBased (), "Java יעיל", "תכנות"); bookRepository.insertbook (ספר); ספר = ספר חדש (UUIDs.timeBased (), "קוד נקי", "תכנות"); bookRepository.insertbook (ספר); רשימת ספרים = bookRepository.selectAll (); assertEquals (2, books.size ()); assertTrue (books.stream (). anyMatch (b -> b.getTitle () .equals ("Java יעיל"))); assertTrue (books.stream (). anyMatch (b -> b.getTitle () .equals ("קוד נקי"))); }

הכל בסדר עד עכשיו, אבל דבר אחד צריך להתממש. התחלנו לעבוד עם שולחן ספרים, אבל בינתיים, כדי לספק את ה בחר שאילתה מאת כותרת היה עלינו ליצור טבלה נוספת בשם booksByTitle.

שתי הטבלאות זהות המכילות עמודות כפולות, אך הכנסנו רק נתונים ל- booksByTitle שולחן. כתוצאה מכך, הנתונים בשתי טבלאות אינם עקביים כרגע.

נוכל לפתור זאת באמצעות קבוצה שאילתה, הכוללת שתי הצהרות הוספה, אחת לכל טבלה. א קבוצה שאילתה מבצעת הצהרות DML מרובות כפעולה אחת.

דוגמה לשאילתה כזו מובאת:

הוסף חלל פומביBookBatch (ספר ספרים) {StringBuilder sb = StringBuilder חדש ("BATCH 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 ("החל אצווה;"); שאילתת מחרוזת = sb.toString (); session.execute (שאילתה); }

שוב אנו בודקים את תוצאות שאילתת האצווה כך:

@Test ציבורי בטל כאשרAddingANewBookBatch_ThenBookAddedInAllTables () {bookRepository.createTable (); bookRepository.createTableBooksByTitle (); כותרת מחרוזת = "Java יעילה"; ספר ספרים = ספר חדש (UUIDs.timeBased (), כותרת, "תכנות"); bookRepository.insertBookBatch (ספר); רשימת ספרים = bookRepository.selectAll (); assertEquals (1, books.size ()); assertTrue (books.stream (). anyMatch (b -> b.getTitle (). שווה ("Java יעיל"))); רשימת booksByTitle = bookRepository.selectAllBookByTitle (); assertEquals (1, booksByTitle.size ()); assertTrue (booksByTitle.stream (). anyMatch (b -> b.getTitle (). שווה ("Java יעיל")); }

הערה: החל מגרסה 3.0, קיימת תכונה חדשה בשם "תצוגות ממונעות", בה אנו עשויים להשתמש במקום קבוצה שאילתות. דוגמה מתועדת היטב עבור "תצוגות ממומשות" זמינה כאן.

3.7. מחיקת משפחת הטורים

הקוד שלהלן מראה כיצד למחוק טבלה:

בטל ציבורי מחק () {StringBuilder sb = StringBuilder חדש ("טפטוף טבלה אם קיים"). הוסף (TABLE_NAME); שאילתת מחרוזת = sb.toString (); session.execute (שאילתה); }

בחירת טבלה שאינה קיימת במרחב המפתחות גורמת ל- InvalidQueryException: ספרי שולחן לא מוגדרים:

@Test (צפוי = InvalidQueryException.class) בטל פומבי כאשר מחיקהATable_thenUnconfiguredTable () {bookRepository.createTable (); bookRepository.deleteTable ("ספרים"); session.execute ("SELECT * FROM" + KEYSPACE_NAME + ".books;"); }

3.8. מחיקת ה- Keyspace

לבסוף, בואו נמחק את מרחב המפתחות:

בטל ציבורי deleteKeyspace (String keypaceName) {StringBuilder sb = StringBuilder new ("DROP KEYSPACE") .append (keypaceName); שאילתת מחרוזת = sb.toString (); session.execute (שאילתה); }

ובדוק שמרחב המפתחות נמחק:

@ מבחן ציבורי בטל כאשר מחיקהAKeyspace_thenDoesNotExist () {String keyspaceName = "ספרייה"; schemaRepository.deleteKeyspace (keypaceName); ResultSet result = session.execute ("SELECT * FROM system_schema.keyspaces;"); isKeyspaceCreated = בוליאני = result.all (). stream () .anyMatch (r -> r.getString (0) .equals (keyspaceName.toLowerCase ())); assertFalse (isKeyspaceCreated); }

4. מסקנה

מדריך זה כיסה את השלבים הבסיסיים של חיבור ושימוש במסד הנתונים של קסנדרה עם Java. חלק ממושגי המפתח של מאגר זה נדונו גם במטרה לעזור לך להתחיל.

ניתן למצוא את היישום המלא של מדריך זה בפרויקט Github.