JavaRush /בלוג Java /Random-HE /JDBC או איפה הכל מתחיל
Viacheslav
רָמָה

JDBC או איפה הכל מתחיל

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

מבוא

אחת המטרות העיקריות של שפת תכנות היא אחסון ועיבוד מידע. כדי להבין טוב יותר כיצד פועל אחסון נתונים, כדאי להקדיש מעט זמן לתיאוריה ולארכיטקטורה של יישומים. לדוגמה, אתה יכול לקרוא את הספרות, כלומר את הספר " מדריך תוכנה לאדריכל: הפוך לארכיטקט תוכנה מצליח על ידי יישום קשת אפקטיבית... " מאת ג'וזף איננו. כאמור, יש שכבת נתונים מסוימת או "שכבת נתונים". הוא כולל מקום לאחסון נתונים (לדוגמה, מסד נתונים של SQL) וכלים לעבודה עם מאגר נתונים (למשל, JDBC, עליו נדון). באתר האינטרנט של מיקרוסופט יש גם מאמר: " עיצוב שכבת התמדת תשתית ", המתאר את הפתרון הארכיטקטוני של הפרדת שכבה נוספת משכבת ​​הנתונים - שכבת ההתמדה. במקרה זה, ה-Data Tier היא רמת האחסון של הנתונים עצמם, בעוד ש-Peristence Layer היא רמה כלשהי של הפשטה לעבודה עם נתונים מהאחסון מרמת ה-Data Tier. שכבת ההתמדה יכולה לכלול את תבנית "DAO" או ORMs שונות. אבל ORM הוא נושא לדיון אחר. כפי שאולי כבר הבנתם, שכבת הנתונים הופיעה ראשונה. מאז תקופת JDK 1.1, JDBC (Java DataBase Connectivity - חיבור לבסיסי נתונים ב-Java) הופיע בעולם Java. זהו תקן לאינטראקציה של יישומי Java עם מערכות DBMS שונות, מיושם בצורה של חבילות java.sql ו-javax.sql הכלולות ב-Java SE:
JDBC או איפה הכל מתחיל - 2
תקן זה מתואר על ידי המפרט " JSR 221 JDBC 4.1 API ". מפרט זה אומר לנו שה-API של JDBC מספק גישה פרוגרמטית לבסיסי נתונים יחסיים מתוכנות שנכתבו ב-Java. זה גם אומר שה-API של JDBC הוא חלק מפלטפורמת Java ולכן הוא כלול ב-Java SE ו-Java EE. ה-API של JDBC מסופק בשתי חבילות: java.sql ו-javax.sql. אז בואו נכיר אותם.
JDBC או איפה הכל מתחיל - 3

תחילת העבודה

כדי להבין מהו ה-API של JDBC באופן כללי, אנו זקוקים לאפליקציית Java. הכי נוח להשתמש באחת ממערכות ההרכבה של הפרויקט. לדוגמה, בואו נשתמש ב- Gradle . אתה יכול לקרוא עוד על Gradle בסקירה קצרה: " מבוא קצר עם Gradle ". ראשית, בואו נאתחל פרויקט Gradle חדש. מכיוון שפונקציונליות Gradle מיושמת באמצעות תוספים, עלינו להשתמש ב- " Gradle Build Init Plugin " לאתחול:
gradle init --type java-application
לאחר מכן, בואו נפתח את סקריפט ה-build - קובץ build.gradle , שמתאר את הפרויקט שלנו וכיצד לעבוד איתו. אנו מתעניינים בגוש ה"תלות ", שבו מתוארות התלות - כלומר אותן ספריות/מסגרות/API, שבלעדיהם לא נוכל לעבוד ושבהן אנו תלויים. כברירת מחדל נראה משהו כמו:
dependencies {
    // This dependency is found on compile classpath of this component and consumers.
    implementation 'com.google.guava:guava:26.0-jre'
    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}
למה אנחנו רואים את זה כאן? אלו הן התלות של הפרויקט שלנו, אשר נוצרו אוטומטית על ידי Gradle כאשר יצרנו את הפרויקט. וגם בגלל שגויאבה היא ספרייה נפרדת שאינה כלולה ב-Java SE. JUnit גם אינו כלול ב-Java SE. אבל יש לנו JDBC מחוץ לקופסה, כלומר, הוא חלק מ-Java SE. מסתבר שיש לנו JDBC. גדול. מה עוד אנחנו צריכים? יש תרשים כל כך נפלא:
JDBC או איפה הכל מתחיל - 4
כפי שאנו יכולים לראות, וזה הגיוני, בסיס הנתונים הוא רכיב חיצוני שאינו מקורי ל-Java SE. זה מוסבר בפשטות - יש מספר עצום של מסדי נתונים ואתה יכול לעבוד עם כל אחד. לדוגמה, יש PostgreSQL, Oracle, MySQL, H2. כל אחד ממאגרי המידע הללו מסופק על ידי חברה נפרדת הנקראת ספקי מסדי נתונים. כל מסד נתונים כתוב בשפת התכנות שלו (לאו דווקא Java). על מנת להיות מסוגל לעבוד עם מסד הנתונים מאפליקציית Java, ספק מסד הנתונים כותב דרייבר מיוחד, שהוא מתאם תמונה משלו. תואמי JDBC כאלה (כלומר, אלה שיש להם מנהל התקן JDBC) נקראים גם "מסד נתונים תואם JDBC". כאן אנו יכולים לצייר אנלוגיה עם התקני מחשב. לדוגמה, בפנקס רשימות יש כפתור "הדפס". בכל פעם שאתה לוחץ עליו, התוכנה אומרת למערכת ההפעלה שיישום הפנקס רוצה להדפיס. ויש לך מדפסת. כדי ללמד את מערכת ההפעלה שלך לתקשר בצורה אחידה עם מדפסת Canon או HP, תצטרך מנהלי התקנים שונים. אבל עבורך, כמשתמש, שום דבר לא ישתנה. אתה עדיין תלחץ על אותו כפתור. אותו דבר עם JDBC. אתה מפעיל את אותו קוד, רק שמאגרי מידע שונים עשויים לרוץ מתחת למכסה המנוע. אני חושב שזו גישה מאוד ברורה. כל מנהל התקן JDBC כזה הוא סוג של חפץ, ספרייה, קובץ jar. זו התלות בפרויקט שלנו. לדוגמה, אנו יכולים לבחור את מסד הנתונים " H2 Database " ואז עלינו להוסיף תלות כזו:
dependencies {
    implementation 'com.h2database:h2:1.4.197'
כיצד למצוא תלות וכיצד לתאר אותה מצוין באתרים הרשמיים של ספק מסד הנתונים או ב" Maven Central ". מנהל ההתקן של JDBC אינו מסד נתונים, כפי שאתה מבין. אבל הוא רק מדריך לזה. אבל יש דבר כזה " במאגרי מידע בזיכרון ". אלו הם מסדי נתונים שקיימים בזיכרון למשך כל חיי היישום שלך. בדרך כלל, זה משמש לעתים קרובות למטרות בדיקה או הדרכה. זה מאפשר לך להימנע מהתקנת שרת מסד נתונים נפרד על המחשב. שמאוד מתאים לנו להכיר את JDBC. אז ארגז החול שלנו מוכן ואנחנו מתחילים.
JDBC או איפה הכל מתחיל - 5

חיבור

אז, יש לנו מנהל התקן JDBC, יש לנו JDBC API. כזכור, JDBC הוא ראשי תיבות של Java DataBase Connectivity. לכן, הכל מתחיל ב-Connektivity – היכולת ליצור חיבור. וחיבור הוא חיבור. בואו נפנה שוב לטקסט של מפרט JDBC ונתבונן בתוכן העניינים. בפרק " סקירה כללית של פרק 4 " ( סקירה כללית ) נפנה לסעיף " 4.1 יצירת חיבור " ( יצירת חיבור ) נאמר שיש שתי דרכים להתחבר למסד הנתונים:
  • דרך מנהל התקן
  • דרך DataSource
בוא נתמודד עם DriverManager. כאמור, DriverManager מאפשר להתחבר למסד הנתונים בכתובת ה-URL שצוינה, וגם טוען JDBC Drivers שהוא מצא ב-CLASSPATH (ולפני, לפני JDBC 4.0, היית צריך לטעון את מחלקת הדרייברים בעצמך). ישנו פרק נפרד "פרק 9 חיבורים" על התחברות למסד הנתונים. אנו מעוניינים כיצד להשיג חיבור דרך ה-DriverManager, ולכן אנו מעוניינים בסעיף "9.3 The DriverManager Class". זה מציין כיצד נוכל לגשת למסד הנתונים:
Connection con = DriverManager.getConnection(url, user, passwd);
את הפרמטרים ניתן לקחת מאתר המאגר שבחרנו. במקרה שלנו, זה H2 - " H2 Cheat Sheet ". בואו נעבור לשיעור AppTest שהוכן על ידי Gradle. הוא מכיל בדיקות JUnit. מבחן JUnit הוא שיטה המסומנת בהערה @Test. בדיקות יחידה אינן נושא הסקירה הזו, אז פשוט נגביל את עצמנו להבנה שמדובר בשיטות המתוארות בצורה מסוימת, שמטרתן לבדוק משהו. על פי מפרט JDBC ואתר H2 נבדוק שקיבלנו חיבור למאגר. בוא נכתוב שיטה להשגת חיבור:
private Connection getNewConnection() throws SQLException {
	String url = "jdbc:h2:mem:test";
	String user = "sa";
	String passwd = "sa";
	return DriverManager.getConnection(url, user, passwd);
}
כעת נכתוב בדיקה לשיטה זו שתבדוק שהחיבור אכן נוצר:
@Test
public void shouldGetJdbcConnection() throws SQLException {
	try(Connection connection = getNewConnection()) {
		assertTrue(connection.isValid(1));
		assertFalse(connection.isClosed());
	}
}
בדיקה זו, כאשר היא תבוצע, תוודא שהחיבור שנוצר תקף (נוצר נכון) ושהוא לא סגור. באמצעות שימוש במשאבים נשחרר משאבים כשלא נצטרך אותם יותר. זה יגן עלינו מפני צניחה של קשרים ודליפות זיכרון. מכיוון שכל פעולות עם מסד הנתונים דורשות חיבור, בואו נספק את שיטות הבדיקה הנותרות המסומנות @Test with a Connection בתחילת הבדיקה, אותן נשחרר לאחר הבדיקה. לשם כך, אנו זקוקים לשתי הערות: @Before ו-@After בואו נוסיף שדה חדש למחלקה AppTest שיאחסן את חיבור JDBC לבדיקות:
private static Connection connection;
ובואו נוסיף שיטות חדשות:
@Before
public void init() throws SQLException {
	connection = getNewConnection();
}
@After
public void close() throws SQLException {
	connection.close();
}
כעת, כל שיטת בדיקה מובטחת בעלת חיבור JDBC ואינה חייבת ליצור אותה בעצמה בכל פעם.
JDBC או איפה הכל מתחיל - 6

הצהרות

לאחר מכן אנו מתעניינים בהצהרות או ביטויים. הם מתוארים בתיעוד בפרק " פרק 13 הצהרות ". ראשית, הוא אומר שיש כמה סוגים או סוגים של הצהרות:
  • הצהרה: ביטוי SQL שאינו מכיל פרמטרים
  • PreparedStatement: משפט SQL מוכן המכיל פרמטרים של קלט
  • CallableStatement: ביטוי SQL עם היכולת להשיג ערך החזרה מ-SQL Stored Procedures.
אז, לאחר חיבור, אנו יכולים לבצע בקשה כלשהי במסגרת החיבור הזה. לכן, זה הגיוני שנשיג בתחילה מופע של ביטוי SQL מ-Connection. אתה צריך להתחיל ביצירת טבלה. נתאר את בקשת יצירת הטבלה כמשתנה מחרוזת. איך לעשות את זה? בוא נשתמש במדריך כמו " sqltutorial.org ", " sqlbolt.com ", " postgresqltutorial.com ", " codecademy.com ". בואו נשתמש, למשל, בדוגמה מקורס SQL ב- khanacademy.org . בואו נוסיף שיטה לביצוע ביטוי במסד הנתונים:
private int executeUpdate(String query) throws SQLException {
	Statement statement = connection.createStatement();
	// Для Insert, Update, Delete
	int result = statement.executeUpdate(query);
	return result;
}
בואו נוסיף שיטה ליצירת טבלת בדיקה בשיטה הקודמת:
private void createCustomerTable() throws SQLException {
	String customerTableQuery = "CREATE TABLE customers " +
                "(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)";
	String customerEntryQuery = "INSERT INTO customers " +
                "VALUES (73, 'Brian', 33)";
	executeUpdate(customerTableQuery);
	executeUpdate(customerEntryQuery);
}
עכשיו בואו נבדוק את זה:
@Test
public void shouldCreateCustomerTable() throws SQLException {
	createCustomerTable();
	connection.createStatement().execute("SELECT * FROM customers");
}
כעת נבצע את הבקשה, ואפילו עם פרמטר:
@Test
public void shouldSelectData() throws SQLException {
 	createCustomerTable();
 	String query = "SELECT * FROM customers WHERE name = ?";
	PreparedStatement statement = connection.prepareStatement(query);
	statement.setString(1, "Brian");
	boolean hasResult = statement.execute();
	assertTrue(hasResult);
}
JDBC אינו תומך בפרמטרים בעלי שם עבור PreparedStatement, כך שהפרמטרים עצמם מצוינים על ידי שאלות, ועל ידי ציון הערך אנו מציינים את אינדקס השאלה (החל מ-1, לא אפס). בבדיקה האחרונה קיבלנו אמת כאינדיקציה האם יש תוצאה. אבל איך מיוצגת תוצאת השאילתה ב-API של JDBC? והוא מוצג כ-ResultSet.
JDBC או איפה הכל מתחיל - 7

סט תוצאות

הרעיון של ResultSet מתואר במפרט JDBC API בפרק "פרק 15 ערכות תוצאות". קודם כל, הוא אומר ש-ResultSet מספק שיטות לאחזור ולתפעל את התוצאות של שאילתות שבוצעו. כלומר, אם שיטת הביצוע חזרה לנו נכונה, אז נוכל לקבל ResultSet. הבה נעביר את הקריאה למתודה createCustomerTable() לתוך המתודה init, המסומנת כ-@Before. עכשיו בואו נסיים את מבחן shouldSelectData שלנו:
@Test
public void shouldSelectData() throws SQLException {
	String query = "SELECT * FROM customers WHERE name = ?";
	PreparedStatement statement = connection.prepareStatement(query);
	statement.setString(1, "Brian");
	boolean hasResult = statement.execute();
	assertTrue(hasResult);
	// Обработаем результат
	ResultSet resultSet = statement.getResultSet();
	resultSet.next();
	int age = resultSet.getInt("age");
	assertEquals(33, age);
}
ראוי לציין כאן שהבאה היא שיטה שמזיזה את מה שנקרא "סמן". הסמן ב-ResultSet מצביע על שורה כלשהי. לפיכך, על מנת לקרוא שורה, עליך להציב עליה את הסמן הזה. כאשר הסמן מוזז, שיטת הזזת הסמן מחזירה אמת אם הסמן חוקי (נכון, נכון), כלומר מצביע על נתונים. אם הוא מחזיר false, אז אין נתונים, כלומר, הסמן לא מצביע על הנתונים. אם ננסה לקבל נתונים עם סמן לא חוקי, נקבל את השגיאה: אין נתונים זמינים. מעניין גם שבאמצעות ResultSet אתה יכול לעדכן או אפילו להוסיף שורות:
@Test
public void shouldInsertInResultSet() throws SQLException {
	Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
	ResultSet resultSet = statement.executeQuery("SELECT * FROM customers");
	resultSet.moveToInsertRow();
	resultSet.updateLong("id", 3L);
	resultSet.updateString("name", "John");
	resultSet.updateInt("age", 18);
	resultSet.insertRow();
	resultSet.moveToCurrentRow();
}

RowSet

בנוסף ל-ResultSet, JDBC מציג את הרעיון של RowSet. אתה יכול לקרוא עוד כאן: " JDBC Basics: Using RowSet Objects ". ישנן וריאציות שונות של שימוש. לדוגמה, המקרה הפשוט ביותר עשוי להיראות כך:
@Test
public void shouldUseRowSet() throws SQLException {
 	JdbcRowSet jdbcRs = new JdbcRowSetImpl(connection);
 	jdbcRs.setCommand("SELECT * FROM customers");
	jdbcRs.execute();
	jdbcRs.next();
	String name = jdbcRs.getString("name");
	assertEquals("Brian", name);
}
כפי שאתה יכול לראות, RowSet דומה לסימביוזה של הצהרה (ציינו פקודה דרכה) ופקודה מבוצעת. באמצעותו אנו שולטים בסמן (על ידי קריאה לשיטה הבאה) ומקבלים ממנו נתונים. לא רק גישה זו מעניינת, אלא גם יישומים אפשריים. לדוגמה, CachedRowSet. הוא "מנותק" (כלומר, אינו משתמש בחיבור מתמשך למסד הנתונים) ודורש סנכרון מפורש עם מסד הנתונים:
CachedRowSet jdbcRsCached = new CachedRowSetImpl();
jdbcRsCached.acceptChanges(connection);
אתה יכול לקרוא עוד במדריך באתר אורקל: " שימוש ב-CachedRowSetObjects ".
JDBC או איפה הכל מתחיל - 8

מטא נתונים

בנוסף לשאילתות, חיבור למסד הנתונים (כלומר, מופע של המחלקה Connection) מספק גישה למטא-נתונים - נתונים על האופן שבו מסד הנתונים שלנו מוגדר ומאורגן. אבל ראשית, נזכיר כמה נקודות מפתח: כתובת האתר לחיבור למסד הנתונים שלנו: "jdbc:h2:mem:test". מבחן הוא השם של מסד הנתונים שלנו. עבור ה-API של JDBC, זוהי ספרייה. והשם יהיה באותיות גדולות, כלומר TEST. סכימת ברירת המחדל עבור H2 היא PUBLIC. כעת, בואו נכתוב מבחן שמציג את כל טבלאות המשתמשים. למה מותאם אישית? כי מסדי נתונים מכילים לא רק טבלאות משתמש (אלו שיצרנו בעצמנו באמצעות ביטויי יצירת טבלאות), אלא גם טבלאות מערכת. הם נחוצים כדי לאחסן מידע מערכת על מבנה מסד הנתונים. כל מסד נתונים יכול לאחסן טבלאות מערכת כאלה בצורה שונה. לדוגמה, ב-H2 הם מאוחסנים בסכימה " INFORMATION_SCHEMA ". מעניין לציין ש- INFORMATION SCHEMA היא גישה נפוצה, אבל אורקל הלכה במסלול אחר. אתה יכול לקרוא עוד כאן: " INFORMATION_SCHEMA ו-Oracle ". בואו נכתוב מבחן שמקבל מטא נתונים על טבלאות משתמשים:
@Test
public void shoudGetMetadata() throws SQLException {
	// У нас URL = "jdbc:h2:mem:test", где test - название БД
	// Название БД = catalog
	DatabaseMetaData metaData = connection.getMetaData();
	ResultSet result = metaData.getTables("TEST", "PUBLIC", "%", null);
	List<String> tables = new ArrayList<>();
	while(result.next()) {
		tables.add(result.getString(2) + "." + result.getString(3));
	}
	assertTrue(tables.contains("PUBLIC.CUSTOMERS"));
}
JDBC או איפה הכל מתחיל - 9

בריכת חיבור

למאגר החיבורים במפרט JDBC יש סעיף שנקרא "Chapter 11 Connection Pooling". הוא גם מספק את ההצדקה העיקרית לצורך במאגר חיבורים. כל Coonection הוא חיבור פיזי למסד הנתונים. יצירתו וסגירתו היא עבודה די "יקרה". JDBC מספק רק ממשק API לאיחוד חיבורים. לכן, בחירת היישום נותרה בידינו. לדוגמה, יישומים כאלה כוללים HikariCP . בהתאם לכך, נצטרך להוסיף מאגר לתלות בפרויקט שלנו:
dependencies {
    implementation 'com.h2database:h2:1.4.197'
    implementation 'com.zaxxer:HikariCP:3.3.1'
    testImplementation 'junit:junit:4.12'
}
עכשיו אנחנו צריכים איכשהו להשתמש בבריכה הזו. כדי לעשות זאת, עליך לאתחל את מקור הנתונים, הידוע גם בשם Datasource:
private DataSource getDatasource() {
	HikariConfig config = new HikariConfig();
	config.setUsername("sa");
	config.setPassword("sa");
	config.setJdbcUrl("jdbc:h2:mem:test");
	DataSource ds = new HikariDataSource(config);
	return ds;
}
ובואו נכתוב מבחן לקבלת חיבור מהבריכה:
@Test
public void shouldGetConnectionFromDataSource() throws SQLException {
	DataSource datasource = getDatasource();
	try (Connection con = datasource.getConnection()) {
		assertTrue(con.isValid(1));
	}
}
JDBC או איפה הכל מתחיל - 10

עסקאות

אחד הדברים המעניינים ביותר ב-JDBC הוא עסקאות. במפרט JDBC מוקצה להם הפרק "פרק 10 עסקאות". קודם כל, כדאי להבין מהי עסקה. עסקה היא קבוצה של פעולות רציפות משולבות באופן הגיוני על נתונים, מעובדות או בוטלות כמכלול. מתי מתחילה עסקה בעת שימוש ב-JDBC? כפי שמציין המפרט, זה מטופל ישירות על ידי מנהל התקן JDBC. אבל בדרך כלל, עסקה חדשה מתחילה כאשר הצהרת SQL הנוכחית דורשת טרנזקציה והטרנזקציה עדיין לא נוצרה. מתי העסקה מסתיימת? זה נשלט על ידי התכונה האוטומטית. אם התחייבות אוטומטית מופעלת, העסקה תושלם לאחר שהצהרת SQL תושלם. המשמעות של "בוצע" תלויה בסוג ביטוי SQL:
  • שפת מניפולציה של נתונים, הידועה גם בשם DML (הוספה, עדכון, מחק)
    העסקה הושלמה ברגע שהפעולה הושלמה
  • בחר הצהרות
    העסקה הושלמה כאשר ה-ResultSet נסגר ( ResultSet#close )
  • CallableStatement וביטויים המחזירים תוצאות מרובות
    כאשר כל ה-ResultSets המשויכים נסגרו וכל הפלט התקבל (כולל מספר העדכונים)
כך בדיוק מתנהג ה-API של JDBC. כרגיל, בוא נכתוב מבחן בשביל זה:
@Test
public void shouldCommitTransaction() throws SQLException {
	connection.setAutoCommit(false);
	String query = "INSERT INTO customers VALUES (1, 'Max', 20)";
	connection.createStatement().executeUpdate(query);
	connection.commit();
	Statement statement = connection.createStatement();
 	statement.execute("SELECT * FROM customers");
	ResultSet resultSet = statement.getResultSet();
	int count = 0;
	while(resultSet.next()) {
		count++;
	}
	assertEquals(2, count);
}
זה פשוט. אבל זה נכון כל עוד יש לנו רק עסקה אחת. מה לעשות כשיש כמה כאלה? הם צריכים להיות מבודדים אחד מהשני. לכן, בואו נדבר על רמות בידוד העסקאות וכיצד JDBC מתמודד איתם.
JDBC או איפה הכל מתחיל - 11

רמות בידוד

בואו נפתח את תת-סעיף "10.2 רמות בידוד עסקאות" של מפרט JDBC. כאן, לפני שנמשיך הלאה, הייתי רוצה לזכור דבר כזה כמו ACID. ACID מתאר את הדרישות למערכת עסקאות.
  • אטומיות:
    שום עסקה לא תתחייב חלקית למערכת. או שכל פעולות המשנה שלו יבוצעו, או שאף אחת לא תבוצע.
  • עקביות:
    כל עסקה מוצלחת, בהגדרה, מתעדת רק תוצאות תקפות.
  • בידוד:
    בזמן שעסקה פועלת, עסקאות במקביל לא אמורות להשפיע על התוצאות שלה.
  • עמידות:
    אם עסקה הושלמה בהצלחה, השינויים שבוצעו בה לא יבוטלו עקב תקלה כלשהי.
כשמדברים על רמות בידוד עסקה, אנחנו מדברים על דרישת ה"בידוד". בידוד הוא דרישה יקרה, ולכן בבסיסי נתונים אמיתיים יש מצבים שאינם מבודדים לחלוטין עסקה (רמות בידוד של Repeatable Read ומטה). לוויקיפדיה יש הסבר מצוין לבעיות שיכולות להתעורר בעבודה עם עסקאות. כדאי לקרוא עוד כאן: " בעיות של גישה מקבילה באמצעות עסקאות ." לפני שנכתוב את המבחן שלנו, בואו נשנה מעט את סקריפט ה-Gradle Build שלנו: הוסף בלוק עם מאפיינים, כלומר עם ההגדרות של הפרויקט שלנו:
ext {
    h2Version = '1.3.176' // 1.4.177
    hikariVersion = '3.3.1'
    junitVersion = '4.12'
}
לאחר מכן, אנו משתמשים בזה בגרסאות:
dependencies {
    implementation "com.h2database:h2:${h2Version}"
    implementation "com.zaxxer:HikariCP:${hikariVersion}"
    testImplementation "junit:junit:${junitVersion}"
}
אולי שמתם לב שגרסת h2 הפכה לנמוכה יותר. נראה למה בהמשך. אז איך מיישמים רמות בידוד? הבה נסתכל מיד על דוגמה מעשית קטנה:
@Test
public void shouldGetReadUncommited() throws SQLException {
	Connection first = getNewConnection();
	assertTrue(first.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED));
	first.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
	first.setAutoCommit(false);
	// Транзакиця на подключение. Поэтому первая транзакция с ReadUncommited вносит изменения
	String insertQuery = "INSERT INTO customers VALUES (5, 'Max', 15)";
	first.createStatement().executeUpdate(insertQuery);
	// Вторая транзакция пытается их увидеть
	int rowCount = 0;
	JdbcRowSet jdbcRs = new JdbcRowSetImpl(getNewConnection());
	jdbcRs.setCommand("SELECT * FROM customers");
	jdbcRs.execute();
	while (jdbcRs.next()) {
		rowCount++;
	}
	assertEquals(2, rowCount);
}
מעניין שבדיקה זו עלולה להיכשל אצל ספק שאינו תומך ב-TRANSACTION_READ_UNCOMMITTED (לדוגמה, sqlite או HSQL). ויתכן שרמת העסקה פשוט לא תעבוד. זוכרים שציינו את הגרסה של מנהל ההתקן H2 Database? אם נעלה אותו ל-h2Version = '1.4.177' ומעלה, אז READ UNCOMMITTED יפסיק לעבוד, למרות שלא שינינו את הקוד. זה שוב מוכיח שהבחירה בגרסת הספק והנהג היא לא רק אותיות, היא למעשה תקבע כיצד הבקשות שלך יבוצעו. אתה יכול לקרוא על איך לתקן התנהגות זו בגרסה 1.4.177 וכיצד זה לא עובד בגרסאות גבוהות יותר כאן: " תמיכה ברמת בידוד READ UNCOMMITTED במצב MVStore ".
JDBC או איפה הכל מתחיל - 12

שורה תחתונה

כפי שאנו יכולים לראות, JDBC הוא כלי רב עוצמה בידי Java לעבודה עם מסדי נתונים. אני מקווה שסקירה קצרה זו תעזור לתת לך נקודת התחלה או תעזור לרענן את הזיכרון שלך. ובכן, לחטיף, כמה חומרים נוספים: #ויאצ'סלב
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION